diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index 78142de2043..d91a8e4fec0 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -45,14 +45,14 @@ jobs:
fetch-depth: 0
- name: "🔧 Setup GraalVM CE"
- uses: graalvm/setup-graalvm@v1.2.1
+ uses: graalvm/setup-graalvm@v1.2.2
with:
distribution: 'graalvm'
java-version: ${{ matrix.java }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: "🔧 Setup Gradle"
- uses: gradle/gradle-build-action@v3.3.2
+ uses: gradle/gradle-build-action@v3.4.2
- name: "❓ Optional setup step"
run: |
@@ -78,7 +78,7 @@ jobs:
- name: "📜 Upload binary compatibility check results"
if: matrix.java == '17'
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
+ uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: binary-compatibility-reports
path: "**/build/reports/binary-compatibility-*.html"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 5afb151f4e0..f234f585bd0 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -66,13 +66,13 @@ jobs:
# Store the hash in a file, which is uploaded as a workflow artifact.
sha256sum $ARTIFACTS | base64 -w0 > artifacts-sha256
- name: Upload build artifacts
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
+ uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: gradle-build-outputs
path: build/repo/${{ steps.publish.outputs.group }}/*/${{ steps.publish.outputs.version }}/*
retention-days: 5
- name: Upload artifacts-sha256
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
+ uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: artifacts-sha256
path: artifacts-sha256
@@ -115,7 +115,7 @@ jobs:
artifacts-sha256: ${{ steps.set-hash.outputs.artifacts-sha256 }}
steps:
- name: Download artifacts-sha256
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
+ uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: artifacts-sha256
# The SLSA provenance generator expects the hash digest of artifacts to be passed as a job
@@ -146,11 +146,9 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Checkout repository
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Download artifacts
- # Important: update actions/download-artifact to v4 only when generator_generic_slsa3.yml is also compatible.
- # See https://github.com/slsa-framework/slsa-github-generator/issues/3068
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
+ uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: gradle-build-outputs
path: build/repo
diff --git a/config/checkstyle/custom-suppressions.xml b/config/checkstyle/custom-suppressions.xml
new file mode 100644
index 00000000000..0421e77d823
--- /dev/null
+++ b/config/checkstyle/custom-suppressions.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
index f7d14fb2eb6..73f71b3a499 100644
--- a/config/checkstyle/suppressions.xml
+++ b/config/checkstyle/suppressions.xml
@@ -9,5 +9,4 @@
-
diff --git a/data-azure-cosmos/src/main/java/io/micronaut/data/cosmos/common/CosmosDatabaseInitializer.java b/data-azure-cosmos/src/main/java/io/micronaut/data/cosmos/common/CosmosDatabaseInitializer.java
index bbc0e637611..e5ae1404ff0 100644
--- a/data-azure-cosmos/src/main/java/io/micronaut/data/cosmos/common/CosmosDatabaseInitializer.java
+++ b/data-azure-cosmos/src/main/java/io/micronaut/data/cosmos/common/CosmosDatabaseInitializer.java
@@ -66,6 +66,11 @@
@Requires(property = "azure.cosmos.key")
final class CosmosDatabaseInitializer {
+ // For a limited time, if the query runs against a region or emulator that has not yet been updated with the
+ // new NonStreamingOrderBy query feature the client might run into some issue of not being able to recognize this,
+ // and throw a 400 exception. If the environment variable `AZURE_COSMOS_DISABLE_NON_STREAMING_ORDER_BY` is set to
+ // True to opt out of this new query feature, then OLD query features will be used to operate correctly.
+ private static final String DISABLE_NON_STREAMING_ORDER_BY = "COSMOS.AZURE_COSMOS_DISABLE_NON_STREAMING_ORDER_BY";
private static final Logger LOG = LoggerFactory.getLogger(CosmosDatabaseInitializer.class);
/**
@@ -82,6 +87,7 @@ void initialize(CosmosClient cosmosClient,
@Nullable
CosmosDiagnosticsProcessor cosmosDiagnosticsProcessor,
CosmosDatabaseConfiguration configuration) {
+ System.setProperty(DISABLE_NON_STREAMING_ORDER_BY, Boolean.toString(configuration.isDisableNonStreamingOrderBy()));
if (LOG.isDebugEnabled()) {
LOG.debug("Cosmos Db Initialization Start");
}
diff --git a/data-azure-cosmos/src/main/java/io/micronaut/data/cosmos/config/CosmosDatabaseConfiguration.java b/data-azure-cosmos/src/main/java/io/micronaut/data/cosmos/config/CosmosDatabaseConfiguration.java
index bca7290a4cc..c08c8fcb7b7 100644
--- a/data-azure-cosmos/src/main/java/io/micronaut/data/cosmos/config/CosmosDatabaseConfiguration.java
+++ b/data-azure-cosmos/src/main/java/io/micronaut/data/cosmos/config/CosmosDatabaseConfiguration.java
@@ -51,6 +51,8 @@ public final class CosmosDatabaseConfiguration {
private boolean queryMetricsEnabled = true;
+ private boolean disableNonStreamingOrderBy = false;
+
public ThroughputSettings getThroughput() {
return throughput;
}
@@ -136,6 +138,26 @@ public void setQueryMetricsEnabled(boolean queryMetricsEnabled) {
this.queryMetricsEnabled = queryMetricsEnabled;
}
+ /**
+ * Gets an indicator telling whether non-streaming order by is by default disabled.
+ * By default, it is not disabled currently and users can change it as needed.
+ * Effectively, this value will be set as "COSMOS.AZURE_COSMOS_DISABLE_NON_STREAMING_ORDER_BY" env variable.
+ *
+ * @return the disabled non-streaming order by indicator
+ */
+ public boolean isDisableNonStreamingOrderBy() {
+ return disableNonStreamingOrderBy;
+ }
+
+ /**
+ * Sets an indicator telling whether non-streaming order by is by default disabled.
+ *
+ * @param disableNonStreamingOrderBy the disabled non-streaming order by indicator
+ */
+ public void setDisableNonStreamingOrderBy(boolean disableNonStreamingOrderBy) {
+ this.disableNonStreamingOrderBy = disableNonStreamingOrderBy;
+ }
+
/**
* Throughput settings for database.
*/
diff --git a/data-azure-cosmos/src/test/groovy/io/micronaut/data/azure/AzureCosmosTestProperties.groovy b/data-azure-cosmos/src/test/groovy/io/micronaut/data/azure/AzureCosmosTestProperties.groovy
index 68a92d0430e..fac535d662b 100644
--- a/data-azure-cosmos/src/test/groovy/io/micronaut/data/azure/AzureCosmosTestProperties.groovy
+++ b/data-azure-cosmos/src/test/groovy/io/micronaut/data/azure/AzureCosmosTestProperties.groovy
@@ -33,6 +33,7 @@ trait AzureCosmosTestProperties implements TestPropertyProvider {
System.setProperty("javax.net.ssl.trustStoreType", "PKCS12")
def defaultProps = [
+ 'azure.cosmos.database.disable-non-streaming-order-by' : 'true',
'azure.cosmos.default-gateway-mode' : 'true',
'azure.cosmos.endpoint-discovery-enabled' : 'false',
'azure.cosmos.endpoint' : emulator.getEmulatorEndpoint(),
diff --git a/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/CosmosSqlQueryBuilder2.java b/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/CosmosSqlQueryBuilder2.java
index 471f015e606..783d8b02b3d 100644
--- a/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/CosmosSqlQueryBuilder2.java
+++ b/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/CosmosSqlQueryBuilder2.java
@@ -141,7 +141,7 @@ protected SqlPredicateVisitor createPredicateVisitor(AnnotationMetadata annotati
private static final String ARRAY_CONTAINS = "ARRAY_CONTAINS";
@Override
- public void visitIsNull(QueryPropertyPath propertyPath) {
+ public void visitIsNull(PersistentPropertyPath propertyPath) {
query.append(NOT).append(SPACE).append(IS_DEFINED).append(OPEN_BRACKET);
appendPropertyRef(propertyPath);
query.append(CLOSE_BRACKET).append(SPACE).append(OR).append(SPACE);
@@ -151,7 +151,7 @@ public void visitIsNull(QueryPropertyPath propertyPath) {
}
@Override
- public void visitIsNotNull(QueryPropertyPath propertyPath) {
+ public void visitIsNotNull(PersistentPropertyPath propertyPath) {
query.append(IS_DEFINED).append(OPEN_BRACKET);
appendPropertyRef(propertyPath);
query.append(CLOSE_BRACKET).append(SPACE).append(AND).append(SPACE);
@@ -161,7 +161,7 @@ public void visitIsNotNull(QueryPropertyPath propertyPath) {
}
@Override
- public void visitIsEmpty(QueryPropertyPath propertyPath) {
+ public void visitIsEmpty(PersistentPropertyPath propertyPath) {
query.append(NOT).append(SPACE).append(IS_DEFINED).append(OPEN_BRACKET);
appendPropertyRef(propertyPath);
query.append(CLOSE_BRACKET).append(SPACE).append(OR).append(SPACE);
@@ -173,7 +173,7 @@ public void visitIsEmpty(QueryPropertyPath propertyPath) {
}
@Override
- public void visitIsNotEmpty(QueryPropertyPath propertyPath) {
+ public void visitIsNotEmpty(PersistentPropertyPath propertyPath) {
query.append(IS_DEFINED).append(OPEN_BRACKET);
appendPropertyRef(propertyPath);
query.append(CLOSE_BRACKET).append(SPACE).append(AND).append(SPACE);
@@ -185,11 +185,11 @@ public void visitIsNotEmpty(QueryPropertyPath propertyPath) {
}
@Override
- public void visitArrayContains(QueryPropertyPath leftProperty, Expression> expression) {
+ public void visitArrayContains(PersistentPropertyPath leftProperty, Expression> expression) {
query.append(ARRAY_CONTAINS).append(OPEN_BRACKET);
appendPropertyRef(leftProperty);
query.append(COMMA);
- appendExpression(leftProperty, expression);
+ appendExpression(expression, leftProperty);
query.append(COMMA);
query.append("true").append(CLOSE_BRACKET);
}
diff --git a/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/MongoQueryBuilder2.java b/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/MongoQueryBuilder2.java
index 9f71177f67a..5d501d3405a 100644
--- a/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/MongoQueryBuilder2.java
+++ b/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/MongoQueryBuilder2.java
@@ -37,6 +37,7 @@
import io.micronaut.data.model.jpa.criteria.IPredicate;
import io.micronaut.data.model.jpa.criteria.ISelection;
import io.micronaut.data.model.jpa.criteria.PersistentEntityRoot;
+import io.micronaut.data.model.jpa.criteria.impl.CriteriaUtils;
import io.micronaut.data.model.jpa.criteria.impl.SelectionVisitor;
import io.micronaut.data.model.jpa.criteria.impl.expression.BinaryExpression;
import io.micronaut.data.model.jpa.criteria.impl.expression.FunctionExpression;
@@ -45,6 +46,7 @@
import io.micronaut.data.model.jpa.criteria.impl.expression.UnaryExpression;
import io.micronaut.data.model.jpa.criteria.impl.predicate.ConjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.DisjunctionPredicate;
+import io.micronaut.data.model.jpa.criteria.impl.predicate.LikePredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.NegatedPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.PersistentPropertyInPredicate;
import io.micronaut.data.model.jpa.criteria.impl.selection.AliasedSelection;
@@ -1033,18 +1035,14 @@ public void visitStartsWith(PersistentPropertyPath leftProperty, Expression> e
}
@Override
- public void visitLike(PersistentPropertyPath leftProperty, Expression> expression) {
- handleRegexPropertyExpression(leftProperty, false, false, false, false, expression);
- }
-
- @Override
- public void visitRLike(PersistentPropertyPath leftProperty, Expression> expression) {
- throw new UnsupportedOperationException("RLike is not supported by this implementation.");
- }
-
- @Override
- public void visitILike(PersistentPropertyPath leftProperty, Expression> expression) {
- throw new UnsupportedOperationException("ILike is not supported by this implementation.");
+ public void visit(LikePredicate likePredicate) {
+ if (likePredicate.isCaseInsensitive()) {
+ throw new UnsupportedOperationException("ILike is not supported by this implementation.");
+ }
+ handleRegexPropertyExpression(
+ CriteriaUtils.requireProperty(likePredicate.getExpression()).getPropertyPath(),
+ false, false, false, false,
+ likePredicate.getPattern());
}
@Override
diff --git a/data-hibernate-jpa/src/main/resources/META-INF/native-image/io.micronaut.data/data-hibernate-jpa-graal/reflect-config.json b/data-hibernate-jpa/src/main/resources/META-INF/native-image/io.micronaut.data/data-hibernate-jpa-graal/reflect-config.json
index 1a281b488f4..4ef57458048 100644
--- a/data-hibernate-jpa/src/main/resources/META-INF/native-image/io.micronaut.data/data-hibernate-jpa-graal/reflect-config.json
+++ b/data-hibernate-jpa/src/main/resources/META-INF/native-image/io.micronaut.data/data-hibernate-jpa-graal/reflect-config.json
@@ -19,5 +19,15 @@
"typeReachable": "org.hibernate.event.service.internal.EventListenerGroupImpl"
},
"unsafeAllocated": true
+ },
+ {
+ "name": "org.hibernate.binder.internal.BatchSizeBinder",
+ "methods": [
+ {
+ "name": "",
+ "parameterTypes": [
+ ]
+ }
+ ]
}
]
diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2CursoredPaginationSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2CursoredPaginationSpec.groovy
index a27d646621f..038d4655202 100644
--- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2CursoredPaginationSpec.groovy
+++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2CursoredPaginationSpec.groovy
@@ -43,8 +43,4 @@ class H2CursoredPaginationSpec extends AbstractCursoredPageSpec {
return br
}
- @Override
- void init() {
- pr.deleteAll()
- }
}
diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2many/MultipleOneToManySpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2many/MultipleOneToManySpec.groovy
new file mode 100644
index 00000000000..a7f3aa061c1
--- /dev/null
+++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/one2many/MultipleOneToManySpec.groovy
@@ -0,0 +1,157 @@
+package io.micronaut.data.jdbc.h2.one2many
+
+import io.micronaut.data.annotation.GeneratedValue
+import io.micronaut.data.annotation.Id
+import io.micronaut.data.annotation.Join
+import io.micronaut.data.annotation.MappedEntity
+import io.micronaut.data.jdbc.annotation.JdbcRepository
+import io.micronaut.data.jdbc.h2.H2DBProperties
+import io.micronaut.data.model.query.builder.sql.Dialect
+import io.micronaut.data.repository.CrudRepository
+import io.micronaut.test.extensions.spock.annotation.MicronautTest
+import jakarta.inject.Inject
+import jakarta.persistence.ManyToOne
+import jakarta.persistence.OneToMany
+import spock.lang.Shared
+import spock.lang.Specification
+
+import java.time.Instant
+
+/**
+ * Test case when an entity has more than one one-to-many collection of the same entity.
+ */
+@MicronautTest
+@H2DBProperties
+class MultipleOneToManySpec extends Specification {
+
+ @Shared
+ @Inject
+ MatchRepository matchRepository
+
+ @Shared
+ @Inject
+ TeamRepository teamRepository
+
+ void "test multiple one to many"() {
+ given:
+ def liverpool = teamRepository.save(new Team(name: "Liverpool"))
+ def manchester = teamRepository.save(new Team(name: "Manchester United"))
+ def westHam = teamRepository.save(new Team(name: "West Ham United"))
+ def matchJune1st = matchRepository.save(new Match(date: createDate(2024, 6, 1), location: "Liverpool",
+ homeTeam: liverpool, awayTeam: manchester))
+ matchRepository.save(new Match(date: createDate(2024, 6, 3), location: "Liverpool",
+ homeTeam: liverpool, awayTeam: westHam))
+ matchRepository.save(new Match(date: createDate(2024, 6, 4), location: "Manchester",
+ homeTeam: manchester, awayTeam: liverpool))
+ matchRepository.save(new Match(date: createDate(2024, 6, 5), location: "London",
+ homeTeam: westHam, awayTeam: manchester))
+ when:
+ def match = matchRepository.getById(matchJune1st.id)
+ then:
+ match
+ match.date == matchJune1st.date
+ match.location == matchJune1st.location
+ match.homeTeam != match.awayTeam
+ match.homeTeam.id == liverpool.id
+ match.awayTeam.id == manchester.id
+ when:
+ def team = teamRepository.getById(liverpool.id)
+ then:
+ team
+ team.id == liverpool.id
+ team.name == liverpool.name
+ team.homeMatches.size() == 2
+ team.awayMatches.size() == 1
+ team.homeMatches[0].awayTeam != team.homeMatches[0].homeTeam
+ team.homeMatches[1].awayTeam != team.homeMatches[1].homeTeam
+ team.awayMatches[0].awayTeam != team.awayMatches[0].homeTeam
+ }
+
+ Instant createDate(int year, int month, int day) {
+ Calendar calendar = Calendar.instance
+ calendar.set(Calendar.YEAR, year)
+ calendar.set(Calendar.MONTH, month)
+ calendar.set(Calendar.DAY_OF_MONTH, day)
+ calendar.toInstant()
+ }
+}
+
+@MappedEntity
+class Team {
+ @Id
+ @GeneratedValue
+ Long id
+
+ String name
+
+ @OneToMany(mappedBy = "homeTeam")
+ Set homeMatches
+
+ @OneToMany(mappedBy = "awayTeam")
+ Set awayMatches
+
+ boolean equals(o) {
+ if (this.is(o)) return true
+ if (o == null || getClass() != o.class) return false
+
+ Team team = (Team) o
+
+ if (name != team.name) return false
+
+ return true
+ }
+
+ int hashCode() {
+ return (name != null ? name.hashCode() : 0)
+ }
+}
+
+@MappedEntity
+class Match {
+ @Id
+ @GeneratedValue
+ Long id
+
+ Instant date
+
+ String location
+
+ @ManyToOne(optional = false)
+ Team homeTeam
+
+ @ManyToOne(optional = false)
+ Team awayTeam
+
+ boolean equals(o) {
+ if (this.is(o)) return true
+ if (o == null || getClass() != o.class) return false
+
+ Match match = (Match) o
+
+ if (date != match.date) return false
+ if (location != match.location) return false
+
+ return true
+ }
+
+ int hashCode() {
+ int result
+ result = (date != null ? date.hashCode() : 0)
+ result = 31 * result + (location != null ? location.hashCode() : 0)
+ return result
+ }
+}
+
+@JdbcRepository(dialect = Dialect.H2)
+interface TeamRepository extends CrudRepository {
+ @Join(value = "homeMatches", type = Join.Type.LEFT_FETCH)
+ @Join(value = "awayMatches", type = Join.Type.LEFT_FETCH)
+ Team getById(Long id);
+}
+
+@JdbcRepository(dialect = Dialect.H2)
+interface MatchRepository extends CrudRepository {
+ @Join(value = "homeTeam", type = Join.Type.LEFT_FETCH)
+ @Join(value = "awayTeam", type = Join.Type.LEFT_FETCH)
+ Match getById(Long id);
+}
diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/mysql/MysqlCursoredPaginationSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/mysql/MysqlCursoredPaginationSpec.groovy
index e65c34e7d1b..d637ea7bab2 100644
--- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/mysql/MysqlCursoredPaginationSpec.groovy
+++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/mysql/MysqlCursoredPaginationSpec.groovy
@@ -16,17 +16,12 @@
package io.micronaut.data.jdbc.mysql
import groovy.transform.Memoized
-import io.micronaut.context.ApplicationContext
import io.micronaut.data.tck.repositories.BookRepository
import io.micronaut.data.tck.repositories.PersonRepository
import io.micronaut.data.tck.tests.AbstractCursoredPageSpec
-import spock.lang.AutoCleanup
-import spock.lang.Shared
class MysqlCursoredPaginationSpec extends AbstractCursoredPageSpec implements MySQLTestPropertyProvider {
- @Shared @AutoCleanup ApplicationContext context
-
@Memoized
@Override
PersonRepository getPersonRepository() {
@@ -39,9 +34,4 @@ class MysqlCursoredPaginationSpec extends AbstractCursoredPageSpec implements My
return context.getBean(MySqlBookRepository)
}
- @Override
- void init() {
- context = ApplicationContext.run(properties)
- }
-
}
diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleXECursoredPaginationSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleXECursoredPaginationSpec.groovy
index 6bb64ef6a27..6bc85e23d77 100644
--- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleXECursoredPaginationSpec.groovy
+++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleXECursoredPaginationSpec.groovy
@@ -16,17 +16,12 @@
package io.micronaut.data.jdbc.oraclexe
import groovy.transform.Memoized
-import io.micronaut.context.ApplicationContext
import io.micronaut.data.tck.repositories.BookRepository
import io.micronaut.data.tck.repositories.PersonRepository
import io.micronaut.data.tck.tests.AbstractCursoredPageSpec
-import spock.lang.AutoCleanup
-import spock.lang.Shared
class OracleXECursoredPaginationSpec extends AbstractCursoredPageSpec implements OracleTestPropertyProvider {
- @Shared @AutoCleanup ApplicationContext context
-
@Override
@Memoized
PersonRepository getPersonRepository() {
@@ -39,9 +34,4 @@ class OracleXECursoredPaginationSpec extends AbstractCursoredPageSpec implements
return context.getBean(OracleXEBookRepository)
}
- @Override
- void init() {
- context = ApplicationContext.run(properties)
- }
-
}
diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresCursoredPaginationSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresCursoredPaginationSpec.groovy
index 214b741c505..ddf7f7203c2 100644
--- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresCursoredPaginationSpec.groovy
+++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresCursoredPaginationSpec.groovy
@@ -16,17 +16,11 @@
package io.micronaut.data.jdbc.postgres
import groovy.transform.Memoized
-import io.micronaut.context.ApplicationContext
import io.micronaut.data.tck.repositories.BookRepository
import io.micronaut.data.tck.repositories.PersonRepository
import io.micronaut.data.tck.tests.AbstractCursoredPageSpec
-import spock.lang.AutoCleanup
-import spock.lang.Ignore
-import spock.lang.Shared
-@Ignore("Causes error: 'FATAL: sorry, too many clients already'")
class PostgresCursoredPaginationSpec extends AbstractCursoredPageSpec implements PostgresTestPropertyProvider {
- @Shared @AutoCleanup ApplicationContext context
@Memoized
@Override
@@ -40,8 +34,4 @@ class PostgresCursoredPaginationSpec extends AbstractCursoredPageSpec implements
return context.getBean(PostgresBookRepository)
}
- @Override
- void init() {
- context = ApplicationContext.run(getProperties())
- }
}
diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/sqlserver/SqlServerCursoredPaginationSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/sqlserver/SqlServerCursoredPaginationSpec.groovy
index dc13c07c9cb..f747d0a7467 100644
--- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/sqlserver/SqlServerCursoredPaginationSpec.groovy
+++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/sqlserver/SqlServerCursoredPaginationSpec.groovy
@@ -15,17 +15,12 @@
*/
package io.micronaut.data.jdbc.sqlserver
-import io.micronaut.context.ApplicationContext
import io.micronaut.data.tck.repositories.BookRepository
import io.micronaut.data.tck.repositories.PersonRepository
import io.micronaut.data.tck.tests.AbstractCursoredPageSpec
-import spock.lang.AutoCleanup
-import spock.lang.Shared
class SqlServerCursoredPaginationSpec extends AbstractCursoredPageSpec implements MSSQLTestPropertyProvider {
- @Shared @AutoCleanup ApplicationContext context
-
@Override
PersonRepository getPersonRepository() {
return context.getBean(MSSQLPersonRepository)
@@ -36,8 +31,4 @@ class SqlServerCursoredPaginationSpec extends AbstractCursoredPageSpec implement
return context.getBean(MSBookRepository)
}
- @Override
- void init() {
- context = ApplicationContext.run(properties)
- }
}
diff --git a/data-model/src/main/java/io/micronaut/data/model/CursoredPage.java b/data-model/src/main/java/io/micronaut/data/model/CursoredPage.java
index 5d2b63df38c..950377b6fa2 100644
--- a/data-model/src/main/java/io/micronaut/data/model/CursoredPage.java
+++ b/data-model/src/main/java/io/micronaut/data/model/CursoredPage.java
@@ -126,7 +126,7 @@ default CursoredPageable previousPageable() {
@Override
default @NonNull CursoredPage map(Function function) {
List content = getContent().stream().map(function).collect(Collectors.toList());
- return new DefaultCursoredPage<>(content, getPageable(), getCursors(), getTotalSize());
+ return new DefaultCursoredPage<>(content, getPageable(), getCursors(), hasTotalSize() ? getTotalSize() : null);
}
/**
diff --git a/data-model/src/main/java/io/micronaut/data/model/Page.java b/data-model/src/main/java/io/micronaut/data/model/Page.java
index 1f817ceb811..9b9032f10d7 100644
--- a/data-model/src/main/java/io/micronaut/data/model/Page.java
+++ b/data-model/src/main/java/io/micronaut/data/model/Page.java
@@ -96,7 +96,7 @@ default boolean hasNext() {
@Override
default @NonNull Page map(Function function) {
List content = getContent().stream().map(function).toList();
- return new DefaultPage<>(content, getPageable(), getTotalSize());
+ return new DefaultPage<>(content, getPageable(), hasTotalSize() ? getTotalSize() : null);
}
/**
diff --git a/data-model/src/main/java/io/micronaut/data/model/PersistentEntityUtils.java b/data-model/src/main/java/io/micronaut/data/model/PersistentEntityUtils.java
index 72e02ad86c1..24c1202dc80 100644
--- a/data-model/src/main/java/io/micronaut/data/model/PersistentEntityUtils.java
+++ b/data-model/src/main/java/io/micronaut/data/model/PersistentEntityUtils.java
@@ -28,6 +28,7 @@
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
+import java.util.function.Consumer;
/**
* Persistent entity utils.
@@ -51,7 +52,16 @@ private PersistentEntityUtils() {
* @since 4.2.0
*/
public static boolean isAccessibleWithoutJoin(Association association, PersistentProperty persistentProperty) {
- return association.getAssociatedEntity().getIdentity() == persistentProperty && !association.isForeignKey();
+ PersistentProperty identity = association.getAssociatedEntity().getIdentity();
+ if (identity instanceof Embedded embedded) {
+ for (PersistentProperty property : embedded.getAssociatedEntity().getPersistentProperties()) {
+ if (property == persistentProperty) {
+ return !association.isForeignKey();
+ }
+ }
+
+ }
+ return identity == persistentProperty && !association.isForeignKey();
}
/**
@@ -139,6 +149,12 @@ public static void traversePersistentProperties(PersistentPropertyPath propertyP
traversePersistentProperties(propertyPath.getAssociations(), propertyPath.getProperty(), true, consumerProperty);
}
+ public static void traverse(PersistentPropertyPath propertyPath, Consumer consumer) {
+ BiConsumer, PersistentProperty> consumerProperty
+ = (associations, property) -> consumer.accept(new PersistentPropertyPath(associations, property));
+ traversePersistentProperties(propertyPath.getAssociations(), propertyPath.getProperty(), true, consumerProperty);
+ }
+
public static void traversePersistentProperties(PersistentPropertyPath propertyPath,
boolean traverseEmbedded,
BiConsumer, PersistentProperty> consumerProperty) {
diff --git a/data-model/src/main/java/io/micronaut/data/model/PersistentPropertyPath.java b/data-model/src/main/java/io/micronaut/data/model/PersistentPropertyPath.java
index 4f588e1858f..2c0dca1dd9d 100644
--- a/data-model/src/main/java/io/micronaut/data/model/PersistentPropertyPath.java
+++ b/data-model/src/main/java/io/micronaut/data/model/PersistentPropertyPath.java
@@ -24,6 +24,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
+import java.util.Objects;
import java.util.Optional;
import java.util.StringJoiner;
@@ -38,6 +39,15 @@ public class PersistentPropertyPath {
private final PersistentProperty property;
private String path;
+ /**
+ * Default constructor.
+ *
+ * @param property The property
+ */
+ public PersistentPropertyPath(@NonNull PersistentProperty property) {
+ this(List.of(), property, null);
+ }
+
/**
* Default constructor.
*
@@ -96,13 +106,13 @@ public static PersistentPropertyPath of(List associations, @NonNull
* @return The root bean - possibly modified
*/
public Object setPropertyValue(Object bean, Object value) {
- if (!(property instanceof RuntimePersistentProperty runtimeProperty)) {
+ if (!(property instanceof RuntimePersistentProperty> runtimeProperty)) {
throw new IllegalStateException("Expected runtime property!");
}
return setProperty(associations, runtimeProperty, bean, value);
}
- private Object setProperty(List associations, RuntimePersistentProperty property, Object bean, Object value) {
+ private Object setProperty(List associations, RuntimePersistentProperty> property, Object bean, Object value) {
if (associations.isEmpty()) {
BeanProperty beanProperty = property.getProperty();
return setProperty(beanProperty, bean, value);
@@ -279,4 +289,26 @@ public Optional findNamingStrategy() {
}
return owner.findNamingStrategy();
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PersistentPropertyPath that = (PersistentPropertyPath) o;
+ return Objects.equals(associations, that.associations) && Objects.equals(property, that.property);
+ }
+
+ @Override
+ public int hashCode() {
+ return property.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "PersistentPropertyPath{associations=" + associations + ", property=" + property + '}';
+ }
}
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/PersistentEntityCriteriaBuilder.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/PersistentEntityCriteriaBuilder.java
index e92be06e625..09b67e60a61 100644
--- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/PersistentEntityCriteriaBuilder.java
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/PersistentEntityCriteriaBuilder.java
@@ -78,22 +78,24 @@ public interface PersistentEntityCriteriaBuilder extends CriteriaBuilder {
Predicate isNotEmptyString(Expression expression);
/**
- * Creates a rlike predicate between an expression x and y.
+ * Creates an case-insensitive like predicate.
*
* @param x The expression
- * @param y The expression
+ * @param pattern The pattern
* @return a new predicate
*/
- Predicate rlikeString(Expression x, Expression y);
+ Predicate ilike(Expression x, Expression pattern);
/**
- * Creates an ilike predicate between an expression x and y.
+ * Creates an case-insensitive like predicate.
*
* @param x The expression
- * @param y The expression
+ * @param pattern The pattern
* @return a new predicate
*/
- Predicate ilikeString(Expression x, Expression y);
+ default Predicate ilike(Expression x, String pattern) {
+ return ilike(x, literal(pattern));
+ }
/**
* Checks if the expression x starts with the expression y.
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractCriteriaBuilder.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractCriteriaBuilder.java
index 561ee29aa43..e79de0ced43 100644
--- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractCriteriaBuilder.java
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractCriteriaBuilder.java
@@ -19,9 +19,6 @@
import io.micronaut.core.annotation.NextMajorVersion;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
-import io.micronaut.data.model.DataType;
-import io.micronaut.data.model.JsonDataType;
-import io.micronaut.data.model.PersistentPropertyPath;
import io.micronaut.data.model.jpa.criteria.PersistentEntityCriteriaBuilder;
import io.micronaut.data.model.jpa.criteria.PersistentEntityCriteriaQuery;
import io.micronaut.data.model.jpa.criteria.impl.expression.BinaryExpression;
@@ -32,6 +29,7 @@
import io.micronaut.data.model.jpa.criteria.impl.predicate.ConjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.DisjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.ExpressionBinaryPredicate;
+import io.micronaut.data.model.jpa.criteria.impl.predicate.LikePredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.NegatedPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.PersistentPropertyBetweenPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.PersistentPropertyBinaryPredicate;
@@ -41,8 +39,6 @@
import io.micronaut.data.model.jpa.criteria.impl.predicate.PredicateUnaryOp;
import io.micronaut.data.model.jpa.criteria.impl.expression.UnaryExpression;
import io.micronaut.data.model.jpa.criteria.impl.expression.UnaryExpressionType;
-import io.micronaut.data.model.query.BindingParameter;
-import io.micronaut.data.model.query.builder.QueryParameterBinding;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CollectionJoin;
import jakarta.persistence.criteria.CompoundSelection;
@@ -321,14 +317,8 @@ public Predicate isNotEmptyString(@NonNull Expression expression) {
@Override
@NonNull
- public Predicate rlikeString(@NonNull Expression x, @NonNull Expression y) {
- return new PersistentPropertyBinaryPredicate<>(requireProperty(x), requirePropertyParameterOrLiteral(y), PredicateBinaryOp.RLIKE);
- }
-
- @Override
- @NonNull
- public Predicate ilikeString(@NonNull Expression x, @NonNull Expression y) {
- return new PersistentPropertyBinaryPredicate<>(requireProperty(x), requirePropertyParameterOrLiteral(y), PredicateBinaryOp.ILIKE);
+ public Predicate ilike(@NonNull Expression x, @NonNull Expression pattern) {
+ return new LikePredicate(x, pattern, null, false, true);
}
@Override
@@ -885,40 +875,7 @@ public ParameterExpression parameter(@NonNull Class paramClass, @NonNu
*/
@NonNull
public ParameterExpression parameter(@NonNull Class paramClass, @Nullable String name, @Nullable Object value) {
- return new ParameterExpressionImpl<>(paramClass, name) {
-
- @Override
- public QueryParameterBinding bind(BindingContext bindingContext) {
- String name = bindingContext.getName() == null ? String.valueOf(bindingContext.getIndex()) : bindingContext.getName();
- PersistentPropertyPath outgoingQueryParameterProperty = bindingContext.getOutgoingQueryParameterProperty();
- if (outgoingQueryParameterProperty == null) {
- return new SimpleParameterBinding(name, DataType.forType(paramClass), bindingContext, value);
- }
- return new PropertyPathParameterBinding(name, outgoingQueryParameterProperty, bindingContext, value);
- }
- };
- }
-
- /**
- * Create a new parameter with possible constant value.
- *
- * @param paramClass The param class
- * @param propertyPath The property path
- * @param value The param value
- * @param The param type
- * @return the parameter expression
- * @since 4.9
- */
- @NonNull
- public ParameterExpression parameterOfProperty(@NonNull Class paramClass, @NonNull PersistentPropertyPath propertyPath, @Nullable Object value) {
- return new ParameterExpressionImpl<>(paramClass, propertyPath.getProperty().getName()) {
-
- @Override
- public QueryParameterBinding bind(BindingContext bindingContext) {
- String name = bindingContext.getName() == null ? String.valueOf(bindingContext.getIndex()) : bindingContext.getName();
- return new PropertyPathParameterBinding(name, propertyPath, bindingContext, value);
- }
- };
+ return new DefaultParameterExpression<>(paramClass, name, value);
}
/**
@@ -1033,130 +990,80 @@ public > Expression> values(@NonNull M map)
@Override
@NonNull
- public Predicate like(@NonNull Expression x, @NonNull Expression pattern) {
- return new PersistentPropertyBinaryPredicate<>(requireProperty(x), requirePropertyParameterOrLiteral(pattern), PredicateBinaryOp.LIKE);
+ public Predicate regex(@NonNull Expression x, @NonNull Expression pattern) {
+ return new PersistentPropertyBinaryPredicate<>(requireProperty(x), requirePropertyParameterOrLiteral(pattern), PredicateBinaryOp.REGEX);
}
@Override
@NonNull
- public Predicate regex(@NonNull Expression x, @NonNull Expression pattern) {
- return new PersistentPropertyBinaryPredicate<>(requireProperty(x), requirePropertyParameterOrLiteral(pattern), PredicateBinaryOp.REGEX);
+ public Predicate like(@NonNull Expression x, @NonNull Expression pattern) {
+ return new LikePredicate(x, pattern, null, false);
}
@Override
@NonNull
public Predicate like(@NonNull Expression x, @NonNull String pattern) {
- return new PersistentPropertyBinaryPredicate<>(requireProperty(x), literal(pattern), PredicateBinaryOp.LIKE);
+ return new LikePredicate(x, literal(pattern), null, false);
}
- /**
- * Not supported yet.
- *
- * {@inheritDoc}
- */
@Override
@NonNull
public Predicate like(@NonNull Expression x, @NonNull Expression pattern, @NonNull Expression escapeChar) {
- throw notSupportedOperation();
+ return new LikePredicate(x, pattern, escapeChar, false);
}
- /**
- * Not supported yet.
- *
- * {@inheritDoc}
- */
@Override
@NonNull
public Predicate like(@NonNull Expression x, @NonNull Expression pattern, char escapeChar) {
- throw notSupportedOperation();
+ return new LikePredicate(x, pattern, literal(escapeChar), false);
}
- /**
- * Not supported yet.
- *
- * {@inheritDoc}
- */
@Override
@NonNull
public Predicate like(@NonNull Expression x, @NonNull String pattern, @NonNull Expression escapeChar) {
- throw notSupportedOperation();
+ return new LikePredicate(x, literal(pattern), escapeChar, false);
}
- /**
- * Not supported yet.
- *
- * {@inheritDoc}
- */
@Override
@NonNull
public Predicate like(@NonNull Expression x, @NonNull String pattern, char escapeChar) {
- throw notSupportedOperation();
+ return new LikePredicate(x, literal(pattern), literal(escapeChar), false);
}
- /**
- * Not supported yet.
- *
- * {@inheritDoc}
- */
@Override
@NonNull
public Predicate notLike(@NonNull Expression x, @NonNull Expression pattern) {
- throw notSupportedOperation();
+ return new LikePredicate(x, pattern, null, true);
}
- /**
- * Not supported yet.
- *
- * {@inheritDoc}
- */
@Override
@NonNull
public Predicate notLike(@NonNull Expression x, @NonNull String pattern) {
- throw notSupportedOperation();
+ return new LikePredicate(x, literal(pattern), null, true);
}
- /**
- * Not supported yet.
- *
- * {@inheritDoc}
- */
@Override
@NonNull
public Predicate notLike(@NonNull Expression x, @NonNull Expression pattern, @NonNull Expression escapeChar) {
- throw notSupportedOperation();
+ return new LikePredicate(x, pattern, escapeChar, true);
}
- /**
- * Not supported yet.
- *
- * {@inheritDoc}
- */
@Override
@NonNull
public Predicate notLike(@NonNull Expression x, @NonNull Expression pattern, char escapeChar) {
- throw notSupportedOperation();
+ return new LikePredicate(x, pattern, literal(escapeChar), true);
}
- /**
- * Not supported yet.
- *
- * {@inheritDoc}
- */
@Override
@NonNull
public Predicate notLike(@NonNull Expression x, @NonNull String pattern, @NonNull Expression escapeChar) {
- throw notSupportedOperation();
+ return new LikePredicate(x, literal(pattern), escapeChar, true);
}
- /**
- * Not supported yet.
- *
- * {@inheritDoc}
- */
@Override
@NonNull
public Predicate notLike(@NonNull Expression x, @NonNull String pattern, char escapeChar) {
- throw notSupportedOperation();
+ return new LikePredicate(x, literal(pattern), literal(escapeChar), true);
}
@Override
@@ -1613,75 +1520,4 @@ public Expression round(Expression x, Integer n) {
throw notSupportedOperation();
}
- private record PropertyPathParameterBinding(String getName,
- PersistentPropertyPath outgoingQueryParameterProperty,
- BindingParameter.BindingContext bindingContext,
- @Nullable Object value) implements QueryParameterBinding {
-
- @Override
- public String getKey() {
- return getName;
- }
-
- @Override
- public DataType getDataType() {
- return outgoingQueryParameterProperty.getProperty().getDataType();
- }
-
- @Override
- public JsonDataType getJsonDataType() {
- return outgoingQueryParameterProperty.getProperty().getJsonDataType();
- }
-
- @Override
- public String[] getPropertyPath() {
- return outgoingQueryParameterProperty.getArrayPath();
- }
-
- @Override
- public boolean isExpandable() {
- return bindingContext.isExpandable();
- }
-
- @Override
- public Object getValue() {
- return value;
- }
- }
-
- private record SimpleParameterBinding(String getName,
- DataType dataType,
- BindingParameter.BindingContext bindingContext,
- @Nullable Object value) implements QueryParameterBinding {
-
- @Override
- public String getKey() {
- return getName;
- }
-
- @Override
- public DataType getDataType() {
- return dataType;
- }
-
- @Override
- public JsonDataType getJsonDataType() {
- return JsonDataType.DEFAULT;
- }
-
- @Override
- public String[] getPropertyPath() {
- return null;
- }
-
- @Override
- public boolean isExpandable() {
- return bindingContext.isExpandable();
- }
-
- @Override
- public Object getValue() {
- return value;
- }
- }
}
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityCriteriaDelete.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityCriteriaDelete.java
index 10650df77e4..4474d01616c 100644
--- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityCriteriaDelete.java
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityCriteriaDelete.java
@@ -97,7 +97,11 @@ protected QueryModelPredicateVisitor createPredicateVisitor(QueryModel queryMode
@Override
public QueryResult buildQuery(AnnotationMetadata annotationMetadata, QueryBuilder queryBuilder) {
- return queryBuilder.buildDelete(annotationMetadata, getQueryModel());
+ QueryBuilder2 queryBuilder2 = QueryResultPersistentEntityCriteriaQuery.findQueryBuilder2(queryBuilder);
+ if (queryBuilder2 == null) {
+ return queryBuilder.buildDelete(annotationMetadata, getQueryModel());
+ }
+ return buildQuery(annotationMetadata, queryBuilder2);
}
@Override
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityCriteriaQuery.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityCriteriaQuery.java
index 9b174d6ea3d..0f0e2fc23c8 100644
--- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityCriteriaQuery.java
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityCriteriaQuery.java
@@ -37,6 +37,7 @@
import io.micronaut.data.model.jpa.criteria.impl.util.Joiner;
import io.micronaut.data.model.query.JoinPath;
import io.micronaut.data.model.query.QueryModel;
+import io.micronaut.data.model.query.builder.QueryBuilder;
import io.micronaut.data.model.query.builder.QueryBuilder2;
import io.micronaut.data.model.query.builder.QueryResult;
import jakarta.persistence.criteria.CriteriaBuilder;
@@ -91,6 +92,15 @@ protected AbstractPersistentEntityCriteriaQuery(Class resultType, CriteriaBui
this.criteriaBuilder = criteriaBuilder;
}
+ @Override
+ public QueryResult buildQuery(AnnotationMetadata annotationMetadata, QueryBuilder queryBuilder) {
+ QueryBuilder2 queryBuilder2 = QueryResultPersistentEntityCriteriaQuery.findQueryBuilder2(queryBuilder);
+ if (queryBuilder2 == null) {
+ return queryBuilder.buildQuery(annotationMetadata, getQueryModel());
+ }
+ return buildQuery(annotationMetadata, queryBuilder2);
+ }
+
@Override
public QueryResult buildQuery(AnnotationMetadata annotationMetadata, QueryBuilder2 queryBuilder) {
SelectQueryDefinitionImpl definition = new SelectQueryDefinitionImpl(
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityCriteriaUpdate.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityCriteriaUpdate.java
index c4ff9ed199e..f280617dc4b 100644
--- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityCriteriaUpdate.java
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityCriteriaUpdate.java
@@ -75,7 +75,11 @@ public abstract class AbstractPersistentEntityCriteriaUpdate implements Persi
@Override
public QueryResult buildQuery(AnnotationMetadata annotationMetadata, QueryBuilder queryBuilder) {
- return queryBuilder.buildUpdate(annotationMetadata, getQueryModel(), updateValues);
+ QueryBuilder2 queryBuilder2 = QueryResultPersistentEntityCriteriaQuery.findQueryBuilder2(queryBuilder);
+ if (queryBuilder2 == null) {
+ return queryBuilder.buildUpdate(annotationMetadata, getQueryModel(), updateValues);
+ }
+ return buildQuery(annotationMetadata, queryBuilder2);
}
@Override
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/CriteriaUtils.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/CriteriaUtils.java
index 43732bbe7ed..18045ac0051 100644
--- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/CriteriaUtils.java
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/CriteriaUtils.java
@@ -197,7 +197,9 @@ public static Set> extractPredicateParameters(Expression<
}
private static void extractPredicateParameters(Expression> predicate, Set> parameters) {
- if (predicate instanceof PersistentPropertyBinaryPredicate> pp) {
+ if (predicate instanceof LiteralExpression>) {
+ return;
+ } else if (predicate instanceof PersistentPropertyBinaryPredicate> pp) {
if (pp.getExpression() instanceof ParameterExpression> parameterExpression) {
parameters.add(parameterExpression);
}
@@ -215,6 +217,8 @@ private static void extractPredicateParameters(Expression> predicate, Set pred : disjunctionPredicate.getPredicates()) {
extractPredicateParameters(pred, parameters);
}
+ } else {
+ throw new IllegalStateException("Unsupported predicate type: " + predicate.getClass().getSimpleName());
}
}
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/DefaultParameterExpression.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/DefaultParameterExpression.java
new file mode 100644
index 00000000000..732d7e793ce
--- /dev/null
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/DefaultParameterExpression.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017-2024 original authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.micronaut.data.model.jpa.criteria.impl;
+
+import io.micronaut.core.annotation.Internal;
+import io.micronaut.core.annotation.NonNull;
+import io.micronaut.core.annotation.Nullable;
+import io.micronaut.data.model.DataType;
+import io.micronaut.data.model.PersistentPropertyPath;
+import io.micronaut.data.model.query.builder.QueryParameterBinding;
+
+/**
+ * The default parameter expression implementation.
+ *
+ * @param The parameter type
+ * @author Denis Stepanov
+ * @since 4.9.0
+ */
+@Internal
+final class DefaultParameterExpression extends ParameterExpressionImpl {
+
+ private final @NonNull Class paramClass;
+ private final @Nullable Object value;
+
+ public DefaultParameterExpression(@NonNull Class paramClass, @Nullable String name, @Nullable Object value) {
+ super(paramClass, name);
+ this.paramClass = paramClass;
+ this.value = value;
+ }
+
+ @Override
+ public QueryParameterBinding bind(BindingContext bindingContext) {
+ String name = bindingContext.getName() == null ? String.valueOf(bindingContext.getIndex()) : bindingContext.getName();
+ PersistentPropertyPath outgoingQueryParameterProperty = bindingContext.getOutgoingQueryParameterProperty();
+ if (outgoingQueryParameterProperty == null) {
+ return new SimpleParameterBinding(name, DataType.forType(paramClass), bindingContext.isExpandable(), value);
+ }
+ return new PropertyPathParameterBinding(name, outgoingQueryParameterProperty, bindingContext.isExpandable(), value);
+ }
+}
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentPropertyPath.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/DefaultPersistentPropertyPath.java
similarity index 81%
rename from data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentPropertyPath.java
rename to data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/DefaultPersistentPropertyPath.java
index ffe2108e33a..60677e2a727 100644
--- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentPropertyPath.java
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/DefaultPersistentPropertyPath.java
@@ -46,15 +46,17 @@
* @since 3.2
*/
@Internal
-public abstract class AbstractPersistentPropertyPath implements PersistentPropertyPath {
+public class DefaultPersistentPropertyPath implements PersistentPropertyPath {
- private final PersistentProperty persistentProperty;
- private final List path;
+ private final io.micronaut.data.model.PersistentPropertyPath propertyPath;
private final CriteriaBuilder criteriaBuilder;
- public AbstractPersistentPropertyPath(PersistentProperty persistentProperty, List path, CriteriaBuilder criteriaBuilder) {
- this.persistentProperty = persistentProperty;
- this.path = path;
+ public DefaultPersistentPropertyPath(PersistentProperty persistentProperty, List associations, CriteriaBuilder criteriaBuilder) {
+ this(new io.micronaut.data.model.PersistentPropertyPath(associations, persistentProperty), criteriaBuilder);
+ }
+
+ public DefaultPersistentPropertyPath(io.micronaut.data.model.PersistentPropertyPath propertyPath, CriteriaBuilder criteriaBuilder) {
+ this.propertyPath = propertyPath;
this.criteriaBuilder = criteriaBuilder;
}
@@ -81,12 +83,17 @@ public Predicate in(Expression> values) {
@Override
public PersistentProperty getProperty() {
- return persistentProperty;
+ return propertyPath.getProperty();
}
@Override
public List getAssociations() {
- return path;
+ return propertyPath.getAssociations();
+ }
+
+ @Override
+ public io.micronaut.data.model.PersistentPropertyPath getPropertyPath() {
+ return propertyPath;
}
@Override
@@ -136,9 +143,6 @@ public void visitExpression(ExpressionVisitor expressionVisitor) {
@Override
public String toString() {
- return "PersistentPropertyPath{" +
- "persistentProperty=" + persistentProperty +
- ", path=" + path +
- '}';
+ return "PersistentPropertyPath{" + propertyPath + '}';
}
}
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/ParameterExpressionImpl.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/ParameterExpressionImpl.java
index d8e2a2ba1b2..e39117b7458 100644
--- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/ParameterExpressionImpl.java
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/ParameterExpressionImpl.java
@@ -123,8 +123,8 @@ public String getAlias() {
@Override
public String toString() {
return "ParameterExpressionImpl{" +
- "type=" + type +
- ", name='" + name + '\'' +
- '}';
+ "type=" + type +
+ ", name='" + name + '\'' +
+ '}';
}
}
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/PredicateVisitor.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/PredicateVisitor.java
index a17adaa35a0..03234df22e9 100644
--- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/PredicateVisitor.java
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/PredicateVisitor.java
@@ -19,6 +19,7 @@
import io.micronaut.data.model.jpa.criteria.impl.predicate.ConjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.DisjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.ExpressionBinaryPredicate;
+import io.micronaut.data.model.jpa.criteria.impl.predicate.LikePredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.NegatedPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.PersistentPropertyBetweenPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.PersistentPropertyBinaryPredicate;
@@ -90,4 +91,11 @@ public interface PredicateVisitor {
*/
void visit(ExpressionBinaryPredicate expressionBinaryPredicate);
+ /**
+ * Visit {@link LikePredicate}.
+ *
+ * @param likePredicate The like predicate
+ */
+ void visit(LikePredicate likePredicate);
+
}
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/PropertyPathParameterBinding.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/PropertyPathParameterBinding.java
new file mode 100644
index 00000000000..d3afec99537
--- /dev/null
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/PropertyPathParameterBinding.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017-2024 original authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.micronaut.data.model.jpa.criteria.impl;
+
+import io.micronaut.core.annotation.Internal;
+import io.micronaut.core.annotation.Nullable;
+import io.micronaut.data.model.DataType;
+import io.micronaut.data.model.JsonDataType;
+import io.micronaut.data.model.PersistentPropertyPath;
+import io.micronaut.data.model.query.builder.QueryParameterBinding;
+
+/**
+ * The property path implementation of {@link QueryParameterBinding}.
+ *
+ * @param getName The name
+ * @param propertyPath The property path
+ * @param isExpandable is expandable
+ * @param value The value
+ * @author Denis Stepanov
+ * @since 4.9.0
+ */
+@Internal
+record PropertyPathParameterBinding(String getName,
+ PersistentPropertyPath propertyPath,
+ boolean isExpandable,
+ @Nullable Object value) implements QueryParameterBinding {
+
+ @Override
+ public String getKey() {
+ return getName;
+ }
+
+ @Override
+ public DataType getDataType() {
+ return propertyPath.getProperty().getDataType();
+ }
+
+ @Override
+ public JsonDataType getJsonDataType() {
+ return propertyPath.getProperty().getJsonDataType();
+ }
+
+ @Override
+ public String[] getPropertyPath() {
+ return propertyPath.getArrayPath();
+ }
+
+ @Override
+ public Object getValue() {
+ return value;
+ }
+}
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/QueryResultPersistentEntityCriteriaQuery.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/QueryResultPersistentEntityCriteriaQuery.java
index 923fb82f3c6..27d881b55c0 100644
--- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/QueryResultPersistentEntityCriteriaQuery.java
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/QueryResultPersistentEntityCriteriaQuery.java
@@ -38,44 +38,47 @@
@Internal
public interface QueryResultPersistentEntityCriteriaQuery {
- default QueryResult buildQuery(AnnotationMetadata annotationMetadata, QueryBuilder queryBuilder) {
- if (queryBuilder.getClass().getSimpleName().equals("CosmosSqlQueryBuilder")) {
+ static QueryBuilder2 findQueryBuilder2(QueryBuilder queryBuilder) {
+ Class extends QueryBuilder> queryBuilderClass = queryBuilder.getClass();
+ if (queryBuilderClass.getSimpleName().equals("CosmosSqlQueryBuilder")) {
// Use new implementation
try {
- return buildQuery(annotationMetadata, (QueryBuilder2) getClass()
+ return (QueryBuilder2) queryBuilderClass
.getClassLoader().loadClass("io.micronaut.data.document.model.query.builder.CosmosSqlQueryBuilder2")
.getDeclaredConstructor(AnnotationMetadata.class)
- .newInstance(((SqlQueryBuilder) queryBuilder).getAnnotationMetadata()));
+ .newInstance(((SqlQueryBuilder) queryBuilder).getAnnotationMetadata());
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
- if (queryBuilder.getClass().getSimpleName().equals("MongoQueryBuilder")) {
+ if (queryBuilderClass.getSimpleName().equals("MongoQueryBuilder")) {
// Use new implementation
try {
- return buildQuery(annotationMetadata, (QueryBuilder2) getClass()
+ return (QueryBuilder2) queryBuilderClass
.getClassLoader().loadClass("io.micronaut.data.document.model.query.builder.MongoQueryBuilder2")
.getDeclaredConstructor()
- .newInstance());
+ .newInstance();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
- if (queryBuilder.getClass() == SqlQueryBuilder.class) {
+ if (queryBuilderClass == SqlQueryBuilder.class) {
// Use new implementation
- return buildQuery(annotationMetadata, newSqlQueryBuilder((SqlQueryBuilder) queryBuilder));
+ return newSqlQueryBuilder((SqlQueryBuilder) queryBuilder);
}
- if (queryBuilder.getClass() == JpaQueryBuilder.class) {
+ if (queryBuilderClass == JpaQueryBuilder.class) {
// Use new implementation
- return buildQuery(annotationMetadata, new JpaQueryBuilder2());
+ return new JpaQueryBuilder2();
}
- return queryBuilder.buildQuery(annotationMetadata, getQueryModel());
+ return null;
}
+ QueryResult buildQuery(AnnotationMetadata annotationMetadata, QueryBuilder queryBuilder);
+
QueryModel getQueryModel();
default QueryResult buildCountQuery(AnnotationMetadata annotationMetadata, QueryBuilder queryBuilder) {
@@ -128,7 +131,7 @@ default QueryResult buildCountQuery(AnnotationMetadata annotationMetadata, Query
throw new UnsupportedOperationException();
}
- private QueryBuilder2 newSqlQueryBuilder(SqlQueryBuilder sqlQueryBuilder) {
+ private static QueryBuilder2 newSqlQueryBuilder(SqlQueryBuilder sqlQueryBuilder) {
// Use new implementation
AnnotationMetadata builderAnnotationMetadata = sqlQueryBuilder.getAnnotationMetadata();
if (builderAnnotationMetadata == null) {
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/SimpleParameterBinding.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/SimpleParameterBinding.java
new file mode 100644
index 00000000000..dfb2847860b
--- /dev/null
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/SimpleParameterBinding.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017-2024 original authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.micronaut.data.model.jpa.criteria.impl;
+
+import io.micronaut.core.annotation.Internal;
+import io.micronaut.core.annotation.Nullable;
+import io.micronaut.data.model.DataType;
+import io.micronaut.data.model.JsonDataType;
+import io.micronaut.data.model.query.builder.QueryParameterBinding;
+
+/**
+ * The simple {@link QueryParameterBinding}.
+ *
+ * @param getName The name
+ * @param dataType The data type
+ * @param isExpandable is expandable
+ * @param value The value
+ * @author Denis Stepanov
+ * @since 4.9.0
+ */
+@Internal
+record SimpleParameterBinding(String getName,
+ DataType dataType,
+ boolean isExpandable,
+ @Nullable Object value) implements QueryParameterBinding {
+
+ @Override
+ public String getKey() {
+ return getName;
+ }
+
+ @Override
+ public DataType getDataType() {
+ return dataType;
+ }
+
+ @Override
+ public JsonDataType getJsonDataType() {
+ return JsonDataType.DEFAULT;
+ }
+
+ @Override
+ public String[] getPropertyPath() {
+ return null;
+ }
+
+ @Override
+ public Object getValue() {
+ return value;
+ }
+}
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/predicate/LikePredicate.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/predicate/LikePredicate.java
new file mode 100644
index 00000000000..03d7cc0b529
--- /dev/null
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/predicate/LikePredicate.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017-2021 original authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.micronaut.data.model.jpa.criteria.impl.predicate;
+
+import io.micronaut.core.annotation.Internal;
+import io.micronaut.core.annotation.Nullable;
+import io.micronaut.data.model.jpa.criteria.impl.PredicateVisitor;
+import jakarta.persistence.criteria.Expression;
+import jakarta.persistence.criteria.Predicate;
+
+/**
+ * The property binary operation predicate implementation.
+ *
+ * @author Denis Stepanov
+ * @since 3.2
+ */
+@Internal
+public final class LikePredicate extends AbstractPredicate {
+
+ private final Expression expression;
+ private final Expression pattern;
+ @Nullable
+ private final Expression escapeChar;
+ private final boolean negated;
+ private final boolean caseInsensitive;
+
+ public LikePredicate(Expression expression, Expression pattern) {
+ this(expression, pattern, null, false, false);
+ }
+
+ public LikePredicate(Expression expression, Expression pattern, Expression escapeChar, boolean negated) {
+ this(expression, pattern, escapeChar, negated, false);
+ }
+
+ public LikePredicate(Expression expression, Expression pattern, Expression escapeChar, boolean negated, boolean caseInsensitive) {
+ this.expression = expression;
+ this.pattern = pattern;
+ this.escapeChar = escapeChar;
+ this.negated = negated;
+ this.caseInsensitive = caseInsensitive;
+ }
+
+ @Override
+ public Predicate not() {
+ return new LikePredicate(expression, pattern, escapeChar, !negated);
+ }
+
+ public Expression getExpression() {
+ return expression;
+ }
+
+ public Expression getPattern() {
+ return pattern;
+ }
+
+ @Nullable
+ public Expression getEscapeChar() {
+ return escapeChar;
+ }
+
+ @Override
+ public boolean isNegated() {
+ return negated;
+ }
+
+ public boolean isCaseInsensitive() {
+ return caseInsensitive;
+ }
+
+ @Override
+ public void visitPredicate(PredicateVisitor predicateVisitor) {
+ predicateVisitor.visit(this);
+ }
+}
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/predicate/PredicateBinaryOp.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/predicate/PredicateBinaryOp.java
index 914c33a1624..014b141d5f8 100644
--- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/predicate/PredicateBinaryOp.java
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/predicate/PredicateBinaryOp.java
@@ -34,9 +34,6 @@ public enum PredicateBinaryOp {
GREATER_THAN_OR_EQUALS,
LESS_THAN,
LESS_THAN_OR_EQUALS,
- RLIKE,
- ILIKE,
- LIKE,
REGEX,
CONTAINS,
CONTAINS_IGNORE_CASE,
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/query/QueryModelPredicateVisitor.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/query/QueryModelPredicateVisitor.java
index 7190efb58cc..7216f3e9082 100644
--- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/query/QueryModelPredicateVisitor.java
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/query/QueryModelPredicateVisitor.java
@@ -29,6 +29,7 @@
import io.micronaut.data.model.jpa.criteria.impl.predicate.ConjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.DisjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.ExpressionBinaryPredicate;
+import io.micronaut.data.model.jpa.criteria.impl.predicate.LikePredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.NegatedPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.PersistentPropertyBetweenPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.PersistentPropertyBinaryPredicate;
@@ -204,12 +205,6 @@ private QueryModel.Criterion getPropertyToValueRestriction(PredicateBinaryOp op,
return Restrictions.endsWith(leftProperty, rightProperty);
case STARTS_WITH:
return Restrictions.startsWith(leftProperty, rightProperty);
- case ILIKE:
- return Restrictions.ilike(leftProperty, rightProperty);
- case RLIKE:
- return Restrictions.rlike(leftProperty, rightProperty);
- case LIKE:
- return Restrictions.like(leftProperty, rightProperty);
case REGEX:
return Restrictions.regex(leftProperty, rightProperty);
case EQUALS_IGNORE_CASE:
@@ -313,6 +308,10 @@ public void visit(PersistentPropertyInPredicate> inValues) {
}
}
+ @Override
+ public void visit(LikePredicate likePredicate) {
+ }
+
private Object asValue(Object value) {
if (value instanceof LiteralExpression> literalExpression) {
return literalExpression.getValue();
diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/util/Joiner.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/util/Joiner.java
index ec81fae6004..56ba2208945 100644
--- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/util/Joiner.java
+++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/util/Joiner.java
@@ -36,6 +36,7 @@
import io.micronaut.data.model.jpa.criteria.impl.predicate.ConjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.DisjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.ExpressionBinaryPredicate;
+import io.micronaut.data.model.jpa.criteria.impl.predicate.LikePredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.NegatedPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.PersistentPropertyBetweenPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.PersistentPropertyBinaryPredicate;
@@ -262,6 +263,11 @@ public void visit(ExpressionBinaryPredicate expressionBinaryPredicate) {
visitPredicateExpression(expressionBinaryPredicate.getRight());
}
+ @Override
+ public void visit(LikePredicate likePredicate) {
+ visitPredicateExpression(likePredicate.getExpression());
+ }
+
/**
* The data structure representing a join.
*/
diff --git a/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/AbstractSqlLikeQueryBuilder2.java b/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/AbstractSqlLikeQueryBuilder2.java
index 1c5da824ce2..76ae8b5f66d 100644
--- a/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/AbstractSqlLikeQueryBuilder2.java
+++ b/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/AbstractSqlLikeQueryBuilder2.java
@@ -18,7 +18,6 @@
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Internal;
-import io.micronaut.core.annotation.NextMajorVersion;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.util.ArgumentUtils;
@@ -36,6 +35,7 @@
import io.micronaut.data.annotation.repeatable.WhereSpecifications;
import io.micronaut.data.model.Association;
import io.micronaut.data.model.DataType;
+import io.micronaut.data.model.Embedded;
import io.micronaut.data.model.JsonDataType;
import io.micronaut.data.model.PersistentAssociationPath;
import io.micronaut.data.model.PersistentEntity;
@@ -47,16 +47,20 @@
import io.micronaut.data.model.jpa.criteria.IPredicate;
import io.micronaut.data.model.jpa.criteria.ISelection;
import io.micronaut.data.model.jpa.criteria.PersistentEntityRoot;
+import io.micronaut.data.model.jpa.criteria.impl.DefaultPersistentPropertyPath;
+import io.micronaut.data.model.jpa.criteria.impl.SelectionVisitor;
import io.micronaut.data.model.jpa.criteria.impl.expression.BinaryExpression;
import io.micronaut.data.model.jpa.criteria.impl.expression.FunctionExpression;
import io.micronaut.data.model.jpa.criteria.impl.expression.IdExpression;
import io.micronaut.data.model.jpa.criteria.impl.expression.LiteralExpression;
-import io.micronaut.data.model.jpa.criteria.impl.SelectionVisitor;
+import io.micronaut.data.model.jpa.criteria.impl.expression.UnaryExpression;
import io.micronaut.data.model.jpa.criteria.impl.predicate.ConjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.DisjunctionPredicate;
+import io.micronaut.data.model.jpa.criteria.impl.predicate.LikePredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.NegatedPredicate;
+import io.micronaut.data.model.jpa.criteria.impl.predicate.PersistentPropertyBinaryPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.PersistentPropertyInPredicate;
-import io.micronaut.data.model.jpa.criteria.impl.expression.UnaryExpression;
+import io.micronaut.data.model.jpa.criteria.impl.predicate.PredicateBinaryOp;
import io.micronaut.data.model.jpa.criteria.impl.selection.AliasedSelection;
import io.micronaut.data.model.jpa.criteria.impl.selection.CompoundSelection;
import io.micronaut.data.model.naming.NamingStrategy;
@@ -446,7 +450,7 @@ protected NamingStrategy getNamingStrategy(PersistentEntity entity) {
* Gets the mapped name from the association using {@link NamingStrategy}.
*
* @param namingStrategy the naming strategy being used
- * @param association the associatioon
+ * @param association the association
* @return the mapped name for the association
*/
@NonNull
@@ -467,6 +471,18 @@ protected String getMappedName(@NonNull NamingStrategy namingStrategy, @NonNull
return namingStrategy.mappedName(associations, property);
}
+ /**
+ * Gets the mapped name from for the list of associations and property using {@link NamingStrategy}.
+ *
+ * @param namingStrategy the naming strategy
+ * @param propertyPath the property path
+ * @return the mappen name for the list of associations and property using given naming strategy
+ */
+ @NonNull
+ protected String getMappedName(@NonNull NamingStrategy namingStrategy, @NonNull PersistentPropertyPath propertyPath) {
+ return namingStrategy.mappedName(propertyPath.getAssociations(), propertyPath.getProperty());
+ }
+
/**
* Builds where clause.
*
@@ -641,7 +657,7 @@ protected void appendOrder(AnnotationMetadata annotationMetadata, SelectQueryDef
}
/**
- * Adds "forUpdate" pisimmistic locking.
+ * Adds "forUpdate" pessimistic locking.
*
* @param queryPosition The query position
* @param definition The definition
@@ -1199,8 +1215,7 @@ protected final void appendExpression(AnnotationMetadata annotationMetadata,
Expression> expression,
boolean isProjection) {
if (expression instanceof io.micronaut.data.model.jpa.criteria.PersistentPropertyPath> persistentPropertyPath) {
- QueryPropertyPath propertyPath = queryState.findProperty(persistentPropertyPath.getPropertyPath());
- appendPropertyRef(annotationMetadata, query, queryState, propertyPath, isProjection);
+ appendPropertyRef(annotationMetadata, query, queryState, persistentPropertyPath.getPropertyPath(), isProjection);
} else if (expression instanceof ParameterExpression> parameterExpression) {
if (expression instanceof BindingParameter bindingParameter) {
queryState.pushParameter(bindingParameter, newBindingContext(null));
@@ -1217,10 +1232,14 @@ protected final void appendExpression(AnnotationMetadata annotationMetadata,
protected final void appendPropertyRef(AnnotationMetadata annotationMetadata,
StringBuilder query,
QueryState queryState,
- QueryPropertyPath propertyPath,
+ PersistentPropertyPath pp,
boolean isProjection) {
+ if (computePropertyPaths() && pp.getProperty() instanceof Embedded) {
+ throw new IllegalArgumentException("Embedded are not allowed as an expression!");
+ }
+ QueryPropertyPath propertyPath = queryState.findProperty(pp);
String tableAlias = propertyPath.getTableAlias();
- String readTransformer = getDataTransformerReadValue(tableAlias, propertyPath.getProperty()).orElse(null);
+ String readTransformer = isProjection ? getDataTransformerReadValue(tableAlias, propertyPath.getProperty()).orElse(null) : null;
if (readTransformer != null) {
query.append(readTransformer);
return;
@@ -1616,7 +1635,7 @@ private QueryPropertyPath findPropertyInternal(PersistentPropertyPath propertyPa
joinAssociation = association;
continue;
}
- if (association != joinAssociation.getAssociatedEntity().getIdentity()) {
+ if (!PersistentEntityUtils.isAccessibleWithoutJoin(joinAssociation, association)) {
lastJoinAlias = getRequiredJoinPathAlias(joinPathJoiner.toString());
// Continue to look for a joined property
joinAssociation = association;
@@ -1645,7 +1664,7 @@ private QueryPropertyPath findPropertyInternal(PersistentPropertyPath propertyPa
@NonNull
private String getRequiredJoinPathAlias(String path) {
if (!isAllowJoins()) {
- throw new IllegalArgumentException("Joins cannot be used in a DELETE or UPDATE operation");
+ throw new IllegalArgumentException("Joins cannot be used in a DELETE or UPDATE operation and path: " + path);
}
return getJoinAlias(path);
}
@@ -1757,7 +1776,7 @@ protected enum QueryPosition {
/**
* The predicate visitor to construct the query.
*/
- protected class SqlPredicateVisitor implements AdvancedPredicateVisitor {
+ protected class SqlPredicateVisitor implements AdvancedPredicateVisitor {
protected final PersistentEntity persistentEntity;
protected final String tableAlias;
@@ -1774,8 +1793,8 @@ protected SqlPredicateVisitor(QueryState queryState, AnnotationMetadata annotati
}
@Override
- public QueryPropertyPath getRequiredProperty(io.micronaut.data.model.jpa.criteria.PersistentPropertyPath> persistentPropertyPath) {
- return queryState.findProperty(persistentPropertyPath.getPropertyPath());
+ public PersistentPropertyPath getRequiredProperty(io.micronaut.data.model.jpa.criteria.PersistentPropertyPath> persistentPropertyPath) {
+ return persistentPropertyPath.getPropertyPath();
}
private void visitPredicate(IExpression expression) {
@@ -1795,9 +1814,14 @@ public void visit(ConjunctionPredicate conjunction) {
if (conjunction.getPredicates().isEmpty()) {
return;
}
- query.append(OPEN_BRACKET);
+ boolean requiresBracket = query.charAt(query.length() - 1) != '(';
+ if (requiresBracket) {
+ query.append(OPEN_BRACKET);
+ }
visitConjunctionPredicates(conjunction.getPredicates());
- query.append(CLOSE_BRACKET);
+ if (requiresBracket) {
+ query.append(CLOSE_BRACKET);
+ }
}
private void visitConjunctionPredicates(Collection extends IExpression> predicates) {
@@ -1859,86 +1883,132 @@ public void visit(NegatedPredicate negate) {
}
@Override
- public void visitEquals(QueryPropertyPath leftProperty, Expression> expression, boolean ignoreCase) {
- if (ignoreCase) {
- appendCaseInsensitiveCriterion(leftProperty, expression, " = ");
+ public void visit(LikePredicate likePredicate) {
+ boolean supportsILike = getDialect() == Dialect.POSTGRES;
+ boolean isCaseInsensitive = !supportsILike && likePredicate.isCaseInsensitive();
+ if (isCaseInsensitive) {
+ query.append("LOWER(");
+ }
+ appendExpression(likePredicate.getExpression());
+ if (isCaseInsensitive) {
+ query.append(")");
+ }
+ if (likePredicate.isNegated()) {
+ query.append(" NOT");
+ }
+ if (likePredicate.isCaseInsensitive() && supportsILike) {
+ query.append(" ILIKE ");
} else {
- PersistentProperty property = leftProperty.getProperty();
- PersistentEntity owner = property.getOwner();
- if (owner.equals(persistentEntity) && leftProperty.getAssociations().isEmpty() && (owner.hasIdentity() && owner.getIdentity() == property)) {
- visitIdEquals(expression);
- } else if (leftProperty.getAssociations().isEmpty() && owner.getVersion() == property) {
- appendPredicateOfVersionEquals(expression);
+ query.append(" LIKE ");
+ }
+ Expression pattern = likePredicate.getPattern();
+ if (isCaseInsensitive) {
+ if (pattern instanceof LiteralExpression literalExpression) {
+ query.append(literalExpression.getValue().toUpperCase());
} else {
- appendCriteriaForOperator(leftProperty, expression, " = ");
+ query.append("LOWER(");
+ appendExpression(pattern);
+ query.append(")");
}
- }
- }
-
- @Override
- public void visitNotEquals(QueryPropertyPath leftProperty, Expression> expression, boolean ignoreCase) {
- if (ignoreCase) {
- appendCaseInsensitiveCriterion(leftProperty, expression, " != ");
} else {
- appendCriteriaForOperator(leftProperty, expression, " != ");
+ appendExpression(pattern);
}
- }
- @Override
- public void visitGreaterThan(QueryPropertyPath leftProperty, Expression> expression) {
- appendCriteriaForOperator(leftProperty, expression, " > ");
+ Expression escapeChar = likePredicate.getEscapeChar();
+ if (escapeChar != null) {
+ query.append(" ESCAPE ");
+ appendExpression(escapeChar);
+ }
}
@Override
- public void visitGreaterThanOrEquals(QueryPropertyPath leftProperty, Expression> expression) {
- appendCriteriaForOperator(leftProperty, expression, " >= ");
+ public void visitEquals(PersistentPropertyPath leftProperty, Expression> expression, boolean ignoreCase) {
+ PersistentProperty property = leftProperty.getProperty();
+ if (computePropertyPaths() && property instanceof Association) {
+ List predicates = new ArrayList<>();
+ PersistentEntityUtils.traverse(leftProperty, pp ->
+ predicates.add(new PersistentPropertyBinaryPredicate<>(
+ new DefaultPersistentPropertyPath<>(pp, null),
+ expression,
+ ignoreCase ? PredicateBinaryOp.EQUALS_IGNORE_CASE : PredicateBinaryOp.EQUALS
+ ))
+ );
+ if (predicates.size() == 1) {
+ predicates.iterator().next().visitPredicate(this);
+ } else {
+ visit(new ConjunctionPredicate(predicates));
+ }
+ } else {
+ if (ignoreCase) {
+ appendCaseInsensitiveOp(leftProperty, expression, " = ");
+ } else {
+ appendSingle(" = ", expression, leftProperty);
+ }
+ }
}
@Override
- public void visitLessThan(QueryPropertyPath leftProperty, Expression> expression) {
- appendCriteriaForOperator(leftProperty, expression, " < ");
+ public void visitNotEquals(PersistentPropertyPath leftProperty, Expression> expression, boolean ignoreCase) {
+ PersistentProperty property = leftProperty.getProperty();
+ if (computePropertyPaths() && property instanceof Association) {
+ List predicates = new ArrayList<>();
+ PersistentEntityUtils.traverse(leftProperty, pp ->
+ predicates.add(new PersistentPropertyBinaryPredicate<>(
+ new DefaultPersistentPropertyPath<>(pp, null),
+ expression,
+ ignoreCase ? PredicateBinaryOp.NOT_EQUALS_IGNORE_CASE : PredicateBinaryOp.NOT_EQUALS
+ ))
+ );
+ if (predicates.size() == 1) {
+ predicates.iterator().next().visitPredicate(this);
+ } else {
+ visit(new ConjunctionPredicate(predicates));
+ }
+ } else {
+ if (ignoreCase) {
+ appendCaseInsensitiveOp(leftProperty, expression, " != ");
+ } else {
+ appendSingle(" != ", expression, leftProperty);
+ }
+ }
}
@Override
- public void visitLessThanOrEquals(QueryPropertyPath leftProperty, Expression> expression) {
- appendCriteriaForOperator(leftProperty, expression, " <= ");
+ public void visitGreaterThan(PersistentPropertyPath leftProperty, Expression> expression) {
+ appendSingle(" > ", expression, leftProperty);
}
@Override
- public void visitLike(QueryPropertyPath leftProperty, Expression> expression) {
- appendCriteriaForOperator(leftProperty, expression, " LIKE ");
+ public void visitGreaterThanOrEquals(PersistentPropertyPath leftProperty, Expression> expression) {
+ appendSingle(" >= ", expression, leftProperty);
}
@Override
- public void visitILike(QueryPropertyPath leftProperty, Expression> expression) {
- if (getDialect() == Dialect.POSTGRES) {
- appendCriteriaForOperator(leftProperty, expression, " ILIKE ");
- } else {
- appendCaseInsensitiveCriterion(leftProperty, expression, " LIKE ");
- }
+ public void visitLessThan(PersistentPropertyPath leftProperty, Expression> expression) {
+ appendSingle(" < ", expression, leftProperty);
}
@Override
- public void visitRLike(QueryPropertyPath leftProperty, Expression> expression) {
- throw new IllegalStateException("Not supported");
+ public void visitLessThanOrEquals(PersistentPropertyPath leftProperty, Expression> expression) {
+ appendSingle(" <= ", expression, leftProperty);
}
@Override
- public void visitStartsWith(QueryPropertyPath leftProperty, Expression> expression, boolean ignoreCase) {
+ public void visitStartsWith(PersistentPropertyPath leftProperty, Expression> expression, boolean ignoreCase) {
appendLikeConcatComparison(leftProperty, expression, ignoreCase, "?", "'%'");
}
@Override
- public void visitContains(QueryPropertyPath leftProperty, Expression> expression, boolean ignoreCase) {
+ public void visitContains(PersistentPropertyPath leftProperty, Expression> expression, boolean ignoreCase) {
appendLikeConcatComparison(leftProperty, expression, ignoreCase, "'%'", "?", "'%'");
}
@Override
- public void visitEndsWith(QueryPropertyPath leftProperty, Expression> expression, boolean ignoreCase) {
+ public void visitEndsWith(PersistentPropertyPath leftProperty, Expression> expression, boolean ignoreCase) {
appendLikeConcatComparison(leftProperty, expression, ignoreCase, "'%'", "?");
}
- private void appendLikeConcatComparison(QueryPropertyPath propertyPath, Expression> expression, boolean ignoreCase, String... parts) {
+ private void appendLikeConcatComparison(PersistentPropertyPath propertyPath, Expression> expression, boolean ignoreCase, String... parts) {
boolean isPostgres = getDialect() == Dialect.POSTGRES;
if (ignoreCase && !isPostgres) {
query.append("LOWER(");
@@ -1957,11 +2027,11 @@ private void appendLikeConcatComparison(QueryPropertyPath propertyPath, Expressi
if (ignoreCase && !isPostgres) {
return (Runnable) () -> {
query.append("LOWER(");
- appendExpression(propertyPath, expression);
+ appendExpression(expression, propertyPath);
query.append(")");
};
} else {
- return (Runnable) () -> appendExpression(propertyPath, expression);
+ return (Runnable) () -> appendExpression(expression, propertyPath);
}
}
return (Runnable) () -> query.append(p);
@@ -1971,165 +2041,112 @@ private void appendLikeConcatComparison(QueryPropertyPath propertyPath, Expressi
@Override
public void visitIdEquals(Expression> expression) {
if (persistentEntity.hasCompositeIdentity()) {
- for (PersistentProperty prop : persistentEntity.getCompositeIdentity()) {
- appendCriteriaForOperator(
- null,
- asQueryPropertyPath(tableAlias, prop),
- expression,
- " = "
- );
- query.append(LOGICAL_AND);
- }
- query.setLength(query.length() - LOGICAL_AND.length());
+ new ConjunctionPredicate(
+ Arrays.stream(persistentEntity.getCompositeIdentity())
+ .map(prop -> {
+ PersistentPropertyPath propertyPath = asPersistentPropertyPath(prop);
+ return new PersistentPropertyBinaryPredicate<>(
+ new DefaultPersistentPropertyPath<>(propertyPath, null),
+ expression,
+ PredicateBinaryOp.EQUALS
+ );
+ }
+ )
+ .toList()
+ ).visitPredicate(this);
} else if (persistentEntity.hasIdentity()) {
- appendCriteriaForOperator(
- asQueryPropertyPath(tableAlias, persistentEntity.getIdentity()),
+ new PersistentPropertyBinaryPredicate<>(
+ new DefaultPersistentPropertyPath<>(new PersistentPropertyPath(persistentEntity.getIdentity()), null),
expression,
- " = "
- );
+ PredicateBinaryOp.EQUALS
+ ).visitPredicate(this);
} else {
throw new IllegalStateException("No ID found for entity: " + persistentEntity.getName());
}
}
- private void appendPredicateOfVersionEquals(Expression> expression) {
- PersistentProperty prop = persistentEntity.getVersion();
- if (prop == null) {
- throw new IllegalStateException("No Version found for entity: " + persistentEntity.getName());
- }
- appendCriteriaForOperator(
- asQueryPropertyPath(tableAlias, prop),
- expression,
- " = "
- );
+ protected final void appendPropertyRef(PersistentPropertyPath propertyPath) {
+ AbstractSqlLikeQueryBuilder2.this.appendPropertyRef(annotationMetadata, query, queryState, propertyPath, false);
}
- protected final void appendPropertyRef(QueryPropertyPath propertyPath) {
- AbstractSqlLikeQueryBuilder2.this.appendPropertyRef(annotationMetadata, query, queryState, propertyPath, false);
+ private void appendSingle(String operator, Expression> expression, @Nullable PersistentPropertyPath propertyPath) {
+ appendPropertyRef(propertyPath);
+ query.append(operator);
+ appendExpression(expression, propertyPath);
}
- private void appendCriteriaForOperator(QueryPropertyPath propertyPath,
- Expression> value,
- String operator) {
- appendCriteriaForOperator(propertyPath.propertyPath, propertyPath, value, operator);
+ private void appendExpression(Expression> expression) {
+ appendExpression(expression, null);
}
- @NextMajorVersion("Remove the trim to have the operators look consistent")
- private void appendCriteriaForOperator(PersistentPropertyPath parameterPropertyPath,
- QueryPropertyPath propertyPath,
- Expression> expression,
- String operator) {
+ protected final void appendExpression(Expression> expression, PersistentPropertyPath propertyPath) {
if (expression instanceof io.micronaut.data.model.jpa.criteria.PersistentPropertyPath> persistentPropertyPath) {
- appendPropertyRef(propertyPath);
- query.append(operator.trim());
- appendPropertyRef(getRequiredProperty(persistentPropertyPath));
+ appendPropertyRef(persistentPropertyPath.getPropertyPath());
} else if (expression instanceof BindingParameter bindingParameter) {
- boolean computePropertyPaths = computePropertyPaths();
- boolean jsonEntity = isJsonEntity(annotationMetadata, persistentEntity);
- if (!computePropertyPaths || jsonEntity) {
- appendPropertyRef(propertyPath);
- query.append(operator);
- queryState.pushParameter(
- bindingParameter,
- newBindingContext(parameterPropertyPath, propertyPath.propertyPath)
- );
- return;
- }
-
- String currentAlias = propertyPath.getTableAlias();
- NamingStrategy namingStrategy = propertyPath.getNamingStrategy();
- boolean shouldEscape = propertyPath.shouldEscape();
- boolean[] needsTrimming = {false};
- PersistentEntityUtils.traversePersistentProperties(propertyPath.getPropertyPath(), (associations, property) -> {
- if (currentAlias != null) {
- query.append(currentAlias).append(DOT);
- }
-
- String columnName = getMappedName(namingStrategy, associations, property);
- if (shouldEscape) {
- columnName = quote(columnName);
- }
- query.append(columnName);
-
- query.append(operator);
- String writeTransformer = getDataTransformerWriteValue(currentAlias, property).orElse(null);
- Runnable pushParameter = () -> {
- queryState.pushParameter(
- bindingParameter,
- newBindingContext(parameterPropertyPath, PersistentPropertyPath.of(associations, property))
- );
- };
- if (writeTransformer != null) {
- appendTransformed(query, writeTransformer, pushParameter);
- } else {
- pushParameter.run();
- }
- query.append(LOGICAL_AND);
- needsTrimming[0] = true;
- });
+ appendBindingParameter(bindingParameter, propertyPath);
+ } else {
+ query.append(asLiteral(expression));
+ }
+ }
- if (needsTrimming[0]) {
- query.setLength(query.length() - LOGICAL_AND.length());
- }
+ private void appendBindingParameter(BindingParameter bindingParameter,
+ @Nullable PersistentPropertyPath entityPropertyPath) {
+ Runnable pushParameter = () -> {
+ queryState.pushParameter(
+ bindingParameter,
+ newBindingContext(null, entityPropertyPath)
+ );
+ };
+ if (entityPropertyPath == null) {
+ pushParameter.run();
} else {
- appendPropertyRef(propertyPath);
- query.append(operator).append(asLiteral(expression));
+ QueryPropertyPath qpp = queryState.findProperty(entityPropertyPath);
+ String writeTransformer = getDataTransformerWriteValue(qpp.tableAlias, entityPropertyPath.getProperty()).orElse(null);
+ if (writeTransformer != null) {
+ appendTransformed(query, writeTransformer, pushParameter);
+ } else {
+ pushParameter.run();
+ }
}
}
- private void appendCaseInsensitiveCriterion(QueryPropertyPath leftProperty, Expression> expression, String operator) {
+ private void appendCaseInsensitiveOp(PersistentPropertyPath leftProperty, Expression> expression, String operator) {
query.append("LOWER(");
appendPropertyRef(leftProperty);
query.append(")")
.append(operator)
.append("LOWER(");
- appendExpression(leftProperty, expression);
+ appendExpression(expression, leftProperty);
query.append(")");
}
- protected final void appendExpression(QueryPropertyPath leftProperty, Expression> expression) {
- if (expression instanceof io.micronaut.data.model.jpa.criteria.PersistentPropertyPath> persistentPropertyPath) {
- appendPropertyRef(getRequiredProperty(persistentPropertyPath));
- } else if (expression instanceof ParameterExpression> parameterExpression) {
- if (expression instanceof BindingParameter bindingParameter) {
- queryState.pushParameter(bindingParameter, newBindingContext(leftProperty.propertyPath));
- } else {
- throw new IllegalArgumentException("Unknown parameter: " + parameterExpression);
- }
- } else if (expression instanceof LiteralExpression> literalExpression) {
- query.append(asLiteral(literalExpression.getValue()));
- } else {
- throw new IllegalArgumentException("Unsupported expression type: " + expression.getClass());
- }
- }
-
@Override
- public void visitIsFalse(QueryPropertyPath propertyPath) {
+ public void visitIsFalse(PersistentPropertyPath propertyPath) {
appendUnaryCondition(" = FALSE", propertyPath);
}
@Override
- public void visitIsNotNull(QueryPropertyPath propertyPath) {
+ public void visitIsNotNull(PersistentPropertyPath propertyPath) {
appendUnaryCondition(" IS NOT NULL", propertyPath);
}
@Override
- public void visitIsNull(QueryPropertyPath propertyPath) {
+ public void visitIsNull(PersistentPropertyPath propertyPath) {
appendUnaryCondition(" IS NULL", propertyPath);
}
@Override
- public void visitIsTrue(QueryPropertyPath propertyPath) {
+ public void visitIsTrue(PersistentPropertyPath propertyPath) {
appendUnaryCondition(" = TRUE", propertyPath);
}
@Override
- public void visitIsEmpty(QueryPropertyPath propertyPath) {
+ public void visitIsEmpty(PersistentPropertyPath propertyPath) {
appendEmptyExpression(" IS NULL" + " " + OR + StringUtils.SPACE, " = ''", " IS EMPTY", propertyPath);
}
@Override
- public void visitIsNotEmpty(QueryPropertyPath propertyPath) {
+ public void visitIsNotEmpty(PersistentPropertyPath propertyPath) {
if (getDialect() == Dialect.ORACLE) {
// Oracle treats blank and null the same
if (propertyPath.getProperty().isAssignable(CharSequence.class)) {
@@ -2147,7 +2164,7 @@ public void visitIsNotEmpty(QueryPropertyPath propertyPath) {
private void appendEmptyExpression(String charSequencePrefix,
String charSequenceSuffix,
String listSuffix,
- QueryPropertyPath propertyPath) {
+ PersistentPropertyPath propertyPath) {
if (propertyPath.getProperty().isAssignable(CharSequence.class)) {
appendPropertyRef(propertyPath);
query.append(charSequencePrefix);
@@ -2159,21 +2176,21 @@ private void appendEmptyExpression(String charSequencePrefix,
}
}
- private void appendUnaryCondition(String sqlOp, QueryPropertyPath propertyPath) {
+ private void appendUnaryCondition(String sqlOp, PersistentPropertyPath propertyPath) {
appendPropertyRef(propertyPath);
query.append(sqlOp);
}
@Override
- public void visitInBetween(QueryPropertyPath property, Expression> from, Expression> to) {
+ public void visitInBetween(PersistentPropertyPath propertyPath, Expression> from, Expression> to) {
query.append(OPEN_BRACKET);
- appendPropertyRef(property);
+ appendPropertyRef(propertyPath);
query.append(" >= ");
- appendExpression(property, from);
+ appendExpression(from, propertyPath);
query.append(LOGICAL_AND);
- appendPropertyRef(property);
+ appendPropertyRef(propertyPath);
query.append(" <= ");
- appendExpression(property, to);
+ appendExpression(to, propertyPath);
query.append(CLOSE_BRACKET);
}
@@ -2183,7 +2200,7 @@ public void visit(PersistentPropertyInPredicate> predicate) {
}
@Override
- public void visitIn(QueryPropertyPath propertyPath, Collection> values, boolean negated) {
+ public void visitIn(PersistentPropertyPath propertyPath, Collection> values, boolean negated) {
if (values.isEmpty()) {
return;
}
@@ -2194,7 +2211,7 @@ public void visitIn(QueryPropertyPath propertyPath, Collection> values, boolea
while (iterator.hasNext()) {
Object value = iterator.next();
if (value instanceof ParameterExpression) {
- BindingParameter.BindingContext bindingContext = newBindingContext(propertyPath.propertyPath);
+ BindingParameter.BindingContext bindingContext = newBindingContext(propertyPath);
if (hasOneParameter) {
bindingContext = bindingContext.expandable();
}
@@ -2307,7 +2324,8 @@ public void visit(LiteralExpression> literalExpression) {
public void visit(UnaryExpression> unaryExpression) {
Expression> expression = unaryExpression.getExpression();
switch (unaryExpression.getType()) {
- case SUM, AVG, MAX, MIN, UPPER, LOWER -> appendFunction(unaryExpression.getType().name(), expression);
+ case SUM, AVG, MAX, MIN, UPPER, LOWER ->
+ appendFunction(unaryExpression.getType().name(), expression);
case COUNT -> {
if (expression instanceof PersistentEntityRoot) {
appendRowCount(tableAlias);
diff --git a/data-model/src/main/java/io/micronaut/data/model/query/impl/AdvancedPredicateVisitor.java b/data-model/src/main/java/io/micronaut/data/model/query/impl/AdvancedPredicateVisitor.java
index 530932d0532..598866e4ced 100644
--- a/data-model/src/main/java/io/micronaut/data/model/query/impl/AdvancedPredicateVisitor.java
+++ b/data-model/src/main/java/io/micronaut/data/model/query/impl/AdvancedPredicateVisitor.java
@@ -109,9 +109,6 @@ default void appendPredicateOfPropertyAndExpression(PredicateBinaryOp op, P left
case GREATER_THAN_OR_EQUALS -> visitGreaterThanOrEquals(leftProperty, expression);
case LESS_THAN -> visitLessThan(leftProperty, expression);
case LESS_THAN_OR_EQUALS -> visitLessThanOrEquals(leftProperty, expression);
- case LIKE -> visitLike(leftProperty, expression);
- case RLIKE -> visitRLike(leftProperty, expression);
- case ILIKE -> visitILike(leftProperty, expression);
case STARTS_WITH -> visitStartsWith(leftProperty, expression, false);
case STARTS_WITH_IGNORE_CASE -> visitStartsWith(leftProperty, expression, true);
case REGEX -> visitRegexp(leftProperty, expression);
@@ -138,12 +135,6 @@ default void visitArrayContains(P leftProperty, Expression> expression) {
void visitStartsWith(P leftProperty, Expression> expression, boolean ignoreCase);
- void visitLike(P leftProperty, Expression> expression);
-
- void visitRLike(P leftProperty, Expression> expression);
-
- void visitILike(P leftProperty, Expression> expression);
-
void visitEquals(P leftProperty, Expression> expression, boolean ignoreCase);
void visitNotEquals(P leftProperty, Expression> expression, boolean ignoreCase);
diff --git a/data-model/src/test/groovy/io/micronaut/data/model/PageSpec.groovy b/data-model/src/test/groovy/io/micronaut/data/model/PageSpec.groovy
index aed6b0bdce3..a69cedb38af 100644
--- a/data-model/src/test/groovy/io/micronaut/data/model/PageSpec.groovy
+++ b/data-model/src/test/groovy/io/micronaut/data/model/PageSpec.groovy
@@ -163,6 +163,28 @@ class PageSpec extends Specification {
deserializedSort == sort
}
+ void "test empty page map"() {
+ when:"Map empty page"
+ def page = Page.empty()
+ def mappedPage = page.map { it }
+ then:"No exception thrown, page is mapped"
+ page.size == -1
+ !page.hasTotalSize()
+ mappedPage.size == -1
+ !mappedPage.hasTotalSize()
+
+ when:"Map empty cursored page"
+ def cursoredPage = CursoredPage.empty()
+ def mappedCursoredPage = cursoredPage.map { it }
+ then:"No exception thrown, cursored page is mapped"
+ cursoredPage.size == -1
+ !cursoredPage.cursors
+ !cursoredPage.hasTotalSize()
+ mappedCursoredPage.size == -1
+ !mappedCursoredPage.cursors
+ !mappedCursoredPage.hasTotalSize()
+ }
+
@EqualsAndHashCode
@ToString
@Serdeable
diff --git a/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/SourcePersistentEntityCriteriaBuilder.java b/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/SourcePersistentEntityCriteriaBuilder.java
index f2e7099aa27..d9de88bf3fb 100644
--- a/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/SourcePersistentEntityCriteriaBuilder.java
+++ b/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/SourcePersistentEntityCriteriaBuilder.java
@@ -17,7 +17,9 @@
import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.annotation.NonNull;
+import io.micronaut.core.annotation.Nullable;
import io.micronaut.data.model.PersistentProperty;
+import io.micronaut.data.model.PersistentPropertyPath;
import io.micronaut.data.model.jpa.criteria.PersistentEntityCriteriaBuilder;
import io.micronaut.inject.ast.ParameterElement;
import jakarta.persistence.criteria.ParameterExpression;
@@ -46,19 +48,23 @@ public interface SourcePersistentEntityCriteriaBuilder extends PersistentEntityC
* Create parameter expression from {@link ParameterElement}.
*
* @param parameterElement The parameter element
+ * @param propertyPath The property path this parameter is representing
* @param The expression type
* @return new parameter
*/
@NonNull
- ParameterExpression parameter(@NonNull ParameterElement parameterElement);
+ ParameterExpression parameter(@NonNull ParameterElement parameterElement,
+ @Nullable PersistentPropertyPath propertyPath);
/**
* Create parameter expression from {@link ParameterElement} that is representing an entity instance.
*
* @param entityParameter The entity parameter element
+ * @param propertyPath The property path this parameter is representing
* @param The expression type
* @return new parameter
*/
@NonNull
- ParameterExpression entityPropertyParameter(@NonNull ParameterElement entityParameter);
+ ParameterExpression entityPropertyParameter(@NonNull ParameterElement entityParameter,
+ @Nullable PersistentPropertyPath propertyPath);
}
diff --git a/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/MethodMatchSourcePersistentEntityCriteriaBuilderImpl.java b/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/MethodMatchSourcePersistentEntityCriteriaBuilderImpl.java
index 12a94cf41d6..bd37f6a0913 100644
--- a/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/MethodMatchSourcePersistentEntityCriteriaBuilderImpl.java
+++ b/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/MethodMatchSourcePersistentEntityCriteriaBuilderImpl.java
@@ -16,8 +16,10 @@
package io.micronaut.data.processor.model.criteria.impl;
import io.micronaut.core.annotation.Internal;
+import io.micronaut.core.annotation.Nullable;
import io.micronaut.data.model.DataType;
import io.micronaut.data.model.PersistentProperty;
+import io.micronaut.data.model.PersistentPropertyPath;
import io.micronaut.data.model.jpa.criteria.PersistentEntityCriteriaQuery;
import io.micronaut.data.model.jpa.criteria.impl.AbstractCriteriaBuilder;
import io.micronaut.data.processor.model.criteria.SourcePersistentEntityCriteriaBuilder;
@@ -74,12 +76,14 @@ public ParameterExpression