From b006f26404f49417c508e53506aa52bf26cedf87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Serralheiro?= Date: Mon, 13 May 2024 14:31:36 +0100 Subject: [PATCH 1/2] Externalizing the QUERY_LOG so the same mechanism can be used for adding the same information to Spans (tracing) --- .../sql/AbstractSqlRepositoryOperations.java | 17 ++++---- .../sql/LoggingSqlExecutionObserver.java | 41 +++++++++++++++++++ .../internal/sql/SqlExecutionObserver.java | 25 +++++++++++ 3 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/LoggingSqlExecutionObserver.java create mode 100644 data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/SqlExecutionObserver.java diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/AbstractSqlRepositoryOperations.java b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/AbstractSqlRepositoryOperations.java index 40474ff2df8..97ec25addd5 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/AbstractSqlRepositoryOperations.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/AbstractSqlRepositoryOperations.java @@ -49,7 +49,6 @@ import io.micronaut.data.model.runtime.RuntimePersistentProperty; import io.micronaut.data.model.runtime.StoredQuery; import io.micronaut.data.operations.HintsCapableRepository; -import io.micronaut.data.runtime.config.DataSettings; import io.micronaut.data.runtime.convert.DataConversionService; import io.micronaut.data.runtime.criteria.RuntimeCriteriaBuilder; import io.micronaut.data.runtime.date.DateTimeProvider; @@ -68,7 +67,6 @@ import io.micronaut.inject.annotation.AnnotationMetadataHierarchy; import io.micronaut.inject.qualifiers.Qualifiers; import io.micronaut.json.JsonMapper; -import org.slf4j.Logger; import java.io.IOException; import java.util.AbstractMap; @@ -101,8 +99,6 @@ public abstract class AbstractSqlRepositoryOperations columnNameResultSetReader; @@ -117,6 +113,7 @@ public abstract class AbstractSqlRepositoryOperations entityInserts = new ConcurrentHashMap<>(10); private final Map entityUpdates = new ConcurrentHashMap<>(10); private final Map associationInserts = new ConcurrentHashMap<>(10); + private final List listeners; /** * Default constructor. @@ -144,7 +141,8 @@ protected AbstractSqlRepositoryOperations( DataConversionService conversionService, AttributeConverterRegistry attributeConverterRegistry, JsonMapper jsonMapper, - SqlJsonColumnMapperProvider sqlJsonColumnMapperProvider) { + SqlJsonColumnMapperProvider sqlJsonColumnMapperProvider, + List listeners) { super(dateTimeProvider, runtimeEntityRegistry, conversionService, attributeConverterRegistry); this.dataSourceName = dataSourceName; this.columnNameResultSetReader = columnNameResultSetReader; @@ -152,6 +150,7 @@ protected AbstractSqlRepositoryOperations( this.preparedStatementWriter = preparedStatementWriter; this.jsonMapper = jsonMapper; this.sqlJsonColumnMapperProvider = sqlJsonColumnMapperProvider; + this.listeners = listeners; Collection> beanDefinitions = beanContext .getBeanDefinitions(Object.class, Qualifiers.byStereotype(Repository.class)); for (BeanDefinition beanDefinition : beanDefinitions) { @@ -201,9 +200,7 @@ protected PS prepareStatement(StatementSupplier statementFunction, } String query = sqlPreparedQuery.getQuery(); - if (QUERY_LOG.isDebugEnabled()) { - QUERY_LOG.debug("Executing Query: {}", query); - } + listeners.forEach(listener -> listener.query(query)); final PS ps; try { ps = statementFunction.create(query); @@ -251,8 +248,8 @@ protected void setStatementParameter(PS preparedStatement, int index, DataType d dataType = dialect.getDataType(dataType); - if (QUERY_LOG.isTraceEnabled()) { - QUERY_LOG.trace("Binding parameter at position {} to value {} with data type: {}", index, value, dataType); + for (SqlExecutionObserver listener : listeners) { + listener.parameter(index, value, dataType); } // We want to avoid potential conversion for JSON because mapper already returned value ready to be set as statement parameter diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/LoggingSqlExecutionObserver.java b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/LoggingSqlExecutionObserver.java new file mode 100644 index 00000000000..ce32c37658b --- /dev/null +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/LoggingSqlExecutionObserver.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017-2020 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.runtime.operations.internal.sql; + +import io.micronaut.data.model.DataType; +import io.micronaut.data.runtime.config.DataSettings; +import jakarta.inject.Singleton; +import org.slf4j.Logger; + +@Singleton +public class LoggingSqlExecutionObserver implements SqlExecutionObserver { + + protected static final Logger QUERY_LOG = DataSettings.QUERY_LOG; + + @Override + public void query(String query) { + if (QUERY_LOG.isDebugEnabled()) { + QUERY_LOG.debug("Executing Query: {}", query); + } + } + + @Override + public void parameter(int index, Object value, DataType dataType) { + if (QUERY_LOG.isTraceEnabled()) { + QUERY_LOG.trace("Binding parameter at position {} to value {} with data type: {}", index, value, dataType); + } + } +} diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/SqlExecutionObserver.java b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/SqlExecutionObserver.java new file mode 100644 index 00000000000..e28ef34314a --- /dev/null +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/SqlExecutionObserver.java @@ -0,0 +1,25 @@ +/* + * Copyright 2017-2020 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.runtime.operations.internal.sql; + +import io.micronaut.data.model.DataType; + +public interface SqlExecutionObserver { + + void query(String query); + + void parameter(int index, Object value, DataType datatype); +} From 20487e7f57ff901fb83c2564089b72024b54963f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Serralheiro?= Date: Mon, 13 May 2024 17:47:08 +0100 Subject: [PATCH 2/2] Fixing compilation errors and adding tests --- .../DefaultJdbcRepositoryOperations.java | 27 ++- .../DefaultR2dbcRepositoryOperations.java | 23 ++- .../sql/AbstractSqlRepositoryOperations.java | 10 +- .../sql/LoggingSqlExecutionObserver.java | 7 + .../internal/sql/SqlExecutionObserver.java | 2 + .../data/tck/TestSqlExecutionObserver.groovy | 39 +++++ .../tck/tests/AbstractRepositorySpec.groovy | 155 ++++++++++++------ 7 files changed, 180 insertions(+), 83 deletions(-) create mode 100644 data-tck/src/main/groovy/io/micronaut/data/tck/TestSqlExecutionObserver.groovy diff --git a/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java b/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java index 0166eeb55c2..233efdcf576 100644 --- a/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java +++ b/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java @@ -88,10 +88,7 @@ import io.micronaut.data.runtime.operations.internal.OperationContext; import io.micronaut.data.runtime.operations.internal.SyncCascadeOperations; import io.micronaut.data.runtime.operations.internal.query.BindableParametersStoredQuery; -import io.micronaut.data.runtime.operations.internal.sql.AbstractSqlRepositoryOperations; -import io.micronaut.data.runtime.operations.internal.sql.SqlJsonColumnMapperProvider; -import io.micronaut.data.runtime.operations.internal.sql.SqlPreparedQuery; -import io.micronaut.data.runtime.operations.internal.sql.SqlStoredQuery; +import io.micronaut.data.runtime.operations.internal.sql.*; import io.micronaut.data.runtime.support.AbstractConversionContext; import io.micronaut.json.JsonMapper; import io.micronaut.transaction.TransactionOperations; @@ -198,7 +195,8 @@ public final class DefaultJdbcRepositoryOperations extends AbstractSqlRepository JdbcSchemaHandler schemaHandler, @Nullable JsonMapper jsonMapper, SqlJsonColumnMapperProvider sqlJsonColumnMapperProvider, - List sqlExceptionMapperList) { + List sqlExceptionMapperList, + List observers) { super( dataSourceName, new ColumnNameResultSetReader(conversionService), @@ -210,7 +208,8 @@ public final class DefaultJdbcRepositoryOperations extends AbstractSqlRepository conversionService, attributeConverterRegistry, jsonMapper, - sqlJsonColumnMapperProvider); + sqlJsonColumnMapperProvider, + observers); this.schemaTenantResolver = schemaTenantResolver; this.schemaHandler = schemaHandler; this.connectionOperations = connectionOperations; @@ -538,8 +537,8 @@ public Optional executeUpdate(@NonNull PreparedQuery pq) { try (PreparedStatement ps = prepareStatement(connection::prepareStatement, preparedQuery, true, false)) { preparedQuery.bindParameters(new JdbcParameterBinder(connection, ps, preparedQuery)); int result = ps.executeUpdate(); - if (QUERY_LOG.isTraceEnabled()) { - QUERY_LOG.trace("Update operation updated {} records", result); + for (SqlExecutionObserver observer : observers) { + observer.updatedRecords(result); } if (preparedQuery.isOptimisticLock()) { checkOptimisticLocking(1, result); @@ -847,8 +846,8 @@ public R execute(@NonNull ConnectionCallback callback) { public R prepareStatement(@NonNull String sql, @NonNull PreparedStatementCallback callback) { ArgumentUtils.requireNonNull("sql", sql); ArgumentUtils.requireNonNull("callback", callback); - if (QUERY_LOG.isDebugEnabled()) { - QUERY_LOG.debug("Executing Query: {}", sql); + for (SqlExecutionObserver observer : observers) { + observer.query(sql); } ConnectionContext connectionCtx = getConnectionCtx(); try { @@ -1169,8 +1168,8 @@ private PreparedStatement prepare(Connection connection, SqlStoredQuery st @Override protected void execute() throws SQLException { - if (QUERY_LOG.isDebugEnabled()) { - QUERY_LOG.debug("Executing SQL query: {}", storedQuery.getQuery()); + for (SqlExecutionObserver observer : observers) { + observer.query(storedQuery.getQuery()); } try { if (storedQuery.getOperationType() == StoredQuery.OperationType.INSERT_RETURNING @@ -1292,8 +1291,8 @@ private void setParameters(PreparedStatement stmt, SqlStoredQuery storedQu @Override protected void execute() { - if (QUERY_LOG.isDebugEnabled()) { - QUERY_LOG.debug("Executing SQL query: {}", storedQuery.getQuery()); + for (SqlExecutionObserver observer : observers) { + observer.query(storedQuery.getQuery()); } if (storedQuery.getOperationType() == StoredQuery.OperationType.INSERT_RETURNING || storedQuery.getOperationType() == StoredQuery.OperationType.UPDATE_RETURNING) { diff --git a/data-r2dbc/src/main/java/io/micronaut/data/r2dbc/operations/DefaultR2dbcRepositoryOperations.java b/data-r2dbc/src/main/java/io/micronaut/data/r2dbc/operations/DefaultR2dbcRepositoryOperations.java index 1e1b1536fe5..10e24ead13c 100644 --- a/data-r2dbc/src/main/java/io/micronaut/data/r2dbc/operations/DefaultR2dbcRepositoryOperations.java +++ b/data-r2dbc/src/main/java/io/micronaut/data/r2dbc/operations/DefaultR2dbcRepositoryOperations.java @@ -77,10 +77,7 @@ import io.micronaut.data.runtime.operations.internal.OperationContext; import io.micronaut.data.runtime.operations.internal.ReactiveCascadeOperations; import io.micronaut.data.runtime.operations.internal.query.BindableParametersStoredQuery; -import io.micronaut.data.runtime.operations.internal.sql.AbstractSqlRepositoryOperations; -import io.micronaut.data.runtime.operations.internal.sql.SqlJsonColumnMapperProvider; -import io.micronaut.data.runtime.operations.internal.sql.SqlPreparedQuery; -import io.micronaut.data.runtime.operations.internal.sql.SqlStoredQuery; +import io.micronaut.data.runtime.operations.internal.sql.*; import io.micronaut.data.runtime.support.AbstractConversionContext; import io.micronaut.json.JsonMapper; import io.micronaut.transaction.exceptions.TransactionSystemException; @@ -188,7 +185,8 @@ final class DefaultR2dbcRepositoryOperations extends AbstractSqlRepositoryOperat SqlJsonColumnMapperProvider sqlJsonColumnMapperProvider, List r2dbcExceptionMapperList, @Parameter R2dbcReactorTransactionOperations transactionOperations, - @Parameter ReactorConnectionOperations connectionOperations) { + @Parameter ReactorConnectionOperations connectionOperations, + List observers) { super( dataSourceName, new ColumnNameR2dbcResultReader(conversionService), @@ -200,7 +198,8 @@ final class DefaultR2dbcRepositoryOperations extends AbstractSqlRepositoryOperat conversionService, attributeConverterRegistry, jsonMapper, - sqlJsonColumnMapperProvider); + sqlJsonColumnMapperProvider, + observers); this.connectionFactory = connectionFactory; this.ioExecutorService = executorService; this.schemaTenantResolver = schemaTenantResolver; @@ -545,8 +544,8 @@ public Mono executeUpdate(@NonNull PreparedQuery pq) { preparedQuery.bindParameters(new R2dbcParameterBinder(connection, statement, preparedQuery)); return executeAndGetRowsUpdatedSingle(statement, dialect) .flatMap((Number rowsUpdated) -> { - if (QUERY_LOG.isTraceEnabled()) { - QUERY_LOG.trace("Update operation updated {} records", rowsUpdated); + for (SqlExecutionObserver observer : observers) { + observer.updatedRecords(rowsUpdated); } if (preparedQuery.isOptimisticLock()) { checkOptimisticLocking(1, rowsUpdated); @@ -970,8 +969,8 @@ private Mono executeAndMapEachRowSingle(Statement statement, Dialect dial @Override protected void execute() throws RuntimeException { - if (QUERY_LOG.isDebugEnabled()) { - QUERY_LOG.debug("Executing SQL query: {}", storedQuery.getQuery()); + for (SqlExecutionObserver observer : observers) { + observer.query(storedQuery.getQuery()); } Statement statement = prepare(ctx.connection); setParameters(statement, storedQuery); @@ -1065,8 +1064,8 @@ private void setParameters(Statement stmt, SqlStoredQuery storedQuery) { @Override protected void execute() throws RuntimeException { - if (QUERY_LOG.isDebugEnabled()) { - QUERY_LOG.debug("Executing SQL query: {}", storedQuery.getQuery()); + for (SqlExecutionObserver observer : observers) { + observer.query(storedQuery.getQuery()); } Statement statement; if (hasGeneratedId) { diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/AbstractSqlRepositoryOperations.java b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/AbstractSqlRepositoryOperations.java index 97ec25addd5..2353b113b8b 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/AbstractSqlRepositoryOperations.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/AbstractSqlRepositoryOperations.java @@ -113,7 +113,7 @@ public abstract class AbstractSqlRepositoryOperations entityInserts = new ConcurrentHashMap<>(10); private final Map entityUpdates = new ConcurrentHashMap<>(10); private final Map associationInserts = new ConcurrentHashMap<>(10); - private final List listeners; + protected final List observers; /** * Default constructor. @@ -142,7 +142,7 @@ protected AbstractSqlRepositoryOperations( AttributeConverterRegistry attributeConverterRegistry, JsonMapper jsonMapper, SqlJsonColumnMapperProvider sqlJsonColumnMapperProvider, - List listeners) { + List observers) { super(dateTimeProvider, runtimeEntityRegistry, conversionService, attributeConverterRegistry); this.dataSourceName = dataSourceName; this.columnNameResultSetReader = columnNameResultSetReader; @@ -150,7 +150,7 @@ protected AbstractSqlRepositoryOperations( this.preparedStatementWriter = preparedStatementWriter; this.jsonMapper = jsonMapper; this.sqlJsonColumnMapperProvider = sqlJsonColumnMapperProvider; - this.listeners = listeners; + this.observers = observers; Collection> beanDefinitions = beanContext .getBeanDefinitions(Object.class, Qualifiers.byStereotype(Repository.class)); for (BeanDefinition beanDefinition : beanDefinitions) { @@ -200,7 +200,7 @@ protected PS prepareStatement(StatementSupplier statementFunction, } String query = sqlPreparedQuery.getQuery(); - listeners.forEach(listener -> listener.query(query)); + observers.forEach(listener -> listener.query(query)); final PS ps; try { ps = statementFunction.create(query); @@ -248,7 +248,7 @@ protected void setStatementParameter(PS preparedStatement, int index, DataType d dataType = dialect.getDataType(dataType); - for (SqlExecutionObserver listener : listeners) { + for (SqlExecutionObserver listener : observers) { listener.parameter(index, value, dataType); } diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/LoggingSqlExecutionObserver.java b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/LoggingSqlExecutionObserver.java index ce32c37658b..52820f9e51b 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/LoggingSqlExecutionObserver.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/LoggingSqlExecutionObserver.java @@ -38,4 +38,11 @@ public void parameter(int index, Object value, DataType dataType) { QUERY_LOG.trace("Binding parameter at position {} to value {} with data type: {}", index, value, dataType); } } + + @Override + public void updatedRecords(Number result) { + if (QUERY_LOG.isTraceEnabled()) { + QUERY_LOG.trace("Update operation updated {} records", result); + } + } } diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/SqlExecutionObserver.java b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/SqlExecutionObserver.java index e28ef34314a..651f451a95b 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/SqlExecutionObserver.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/SqlExecutionObserver.java @@ -22,4 +22,6 @@ public interface SqlExecutionObserver { void query(String query); void parameter(int index, Object value, DataType datatype); + + void updatedRecords(Number result); } diff --git a/data-tck/src/main/groovy/io/micronaut/data/tck/TestSqlExecutionObserver.groovy b/data-tck/src/main/groovy/io/micronaut/data/tck/TestSqlExecutionObserver.groovy new file mode 100644 index 00000000000..9e0066e5bdb --- /dev/null +++ b/data-tck/src/main/groovy/io/micronaut/data/tck/TestSqlExecutionObserver.groovy @@ -0,0 +1,39 @@ +package io.micronaut.data.tck + +import io.micronaut.data.model.DataType +import io.micronaut.data.runtime.operations.internal.sql.SqlExecutionObserver +import jakarta.inject.Singleton + +@Singleton +class TestSqlExecutionObserver implements SqlExecutionObserver { + public List invocations = new ArrayList<>() + + @Override + void query(String query) { + invocations.add(new Invocation(query)) + } + + @Override + void parameter(int index, Object value, DataType datatype) { + invocations.last().parameters[index] = value + } + + @Override + void updatedRecords(Number result) { + invocations.last().affected = result + } + + void clear() { + invocations.clear() + } + + class Invocation { + String query + Map parameters = [:] + Number affected + + Invocation(String query) { + this.query = query + } + } +} diff --git a/data-tck/src/main/groovy/io/micronaut/data/tck/tests/AbstractRepositorySpec.groovy b/data-tck/src/main/groovy/io/micronaut/data/tck/tests/AbstractRepositorySpec.groovy index 0995ddf1602..d8da3bcdc5a 100644 --- a/data-tck/src/main/groovy/io/micronaut/data/tck/tests/AbstractRepositorySpec.groovy +++ b/data-tck/src/main/groovy/io/micronaut/data/tck/tests/AbstractRepositorySpec.groovy @@ -23,55 +23,17 @@ import io.micronaut.data.model.CursoredPageable import io.micronaut.data.model.Pageable import io.micronaut.data.model.Sort import io.micronaut.data.model.jpa.criteria.PersistentEntityCriteriaBuilder -import io.micronaut.data.repository.jpa.criteria.CriteriaQueryBuilder -import io.micronaut.data.repository.jpa.criteria.DeleteSpecification -import io.micronaut.data.repository.jpa.criteria.PredicateSpecification -import io.micronaut.data.repository.jpa.criteria.QuerySpecification -import io.micronaut.data.repository.jpa.criteria.UpdateSpecification -import io.micronaut.data.tck.entities.Author -import io.micronaut.data.tck.entities.AuthorBooksDto -import io.micronaut.data.tck.entities.AuthorDtoWithBookDtos -import io.micronaut.data.tck.entities.BasicTypes -import io.micronaut.data.tck.entities.Book -import io.micronaut.data.tck.entities.BookDto -import io.micronaut.data.tck.entities.Car -import io.micronaut.data.tck.entities.Chapter -import io.micronaut.data.tck.entities.City -import io.micronaut.data.tck.entities.Company -import io.micronaut.data.tck.entities.Country -import io.micronaut.data.tck.entities.CountryRegion -import io.micronaut.data.tck.entities.CountryRegionCity -import io.micronaut.data.tck.entities.EntityIdClass -import io.micronaut.data.tck.entities.EntityWithIdClass -import io.micronaut.data.tck.entities.EntityWithIdClass2 -import io.micronaut.data.tck.entities.Face -import io.micronaut.data.tck.entities.Food -import io.micronaut.data.tck.entities.Genre -import io.micronaut.data.tck.entities.Meal -import io.micronaut.data.tck.entities.Nose -import io.micronaut.data.tck.entities.Page -import io.micronaut.data.tck.entities.Person -import io.micronaut.data.tck.entities.Student -import io.micronaut.data.tck.entities.TimezoneBasicTypes +import io.micronaut.data.repository.jpa.criteria.* +import io.micronaut.data.tck.TestSqlExecutionObserver +import io.micronaut.data.tck.entities.* import io.micronaut.data.tck.jdbc.entities.Role import io.micronaut.data.tck.jdbc.entities.UserRole import io.micronaut.data.tck.repositories.* import io.micronaut.transaction.SynchronousTransactionManager import io.micronaut.transaction.TransactionCallback import io.micronaut.transaction.TransactionStatus -import jakarta.persistence.criteria.CriteriaBuilder -import jakarta.persistence.criteria.CriteriaQuery -import jakarta.persistence.criteria.CriteriaUpdate -import jakarta.persistence.criteria.Path -import jakarta.persistence.criteria.Predicate -import jakarta.persistence.criteria.Root -import spock.lang.AutoCleanup -import spock.lang.IgnoreIf -import spock.lang.Issue -import spock.lang.PendingFeature -import spock.lang.Shared -import spock.lang.Specification -import spock.lang.Unroll +import jakarta.persistence.criteria.* +import spock.lang.* import java.sql.Connection import java.time.LocalDate @@ -79,15 +41,8 @@ import java.time.ZoneId import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit -import static io.micronaut.data.tck.repositories.BookSpecifications.hasChapter -import static io.micronaut.data.tck.repositories.BookSpecifications.titleContains -import static io.micronaut.data.tck.repositories.BookSpecifications.titleEquals -import static io.micronaut.data.tck.repositories.BookSpecifications.titleEqualsWithJoin -import static io.micronaut.data.tck.repositories.PersonRepository.Specifications.distinct -import static io.micronaut.data.tck.repositories.PersonRepository.Specifications.idsIn -import static io.micronaut.data.tck.repositories.PersonRepository.Specifications.nameEquals -import static io.micronaut.data.tck.repositories.PersonRepository.Specifications.setIncome -import static io.micronaut.data.tck.repositories.PersonRepository.Specifications.setName +import static io.micronaut.data.tck.repositories.BookSpecifications.* +import static io.micronaut.data.tck.repositories.PersonRepository.Specifications.* abstract class AbstractRepositorySpec extends Specification { @@ -125,6 +80,9 @@ abstract class AbstractRepositorySpec extends Specification { @Shared Optional> transactionManager = context.findBean(SynchronousTransactionManager) + @Shared + TestSqlExecutionObserver observer = context.getBean(TestSqlExecutionObserver) + ApplicationContext getApplicationContext() { return context } @@ -2865,6 +2823,99 @@ abstract class AbstractRepositorySpec extends Specification { entityWithIdClass2Repository.deleteAll() } + void "observer receives inserts"() { + given: + observer.clear() + + when: + bookRepository.save(new Book(title: "Anonymous", totalPages: 400)) + + then: + observer.invocations.size() == 1 + observer.invocations.get(0).query =~ /(?i)insert\s+into\s+.*/ + observer.invocations.get(0).parameters[3] == "Anonymous" + observer.invocations.get(0).parameters[4] == 400 + + cleanup: + cleanupData() + } + + void "observer receives query"() { + given: + observer.clear() + + when: + bookRepository.findById(1) + + then: + observer.invocations.size() == 1 + observer.invocations.get(0).query =~ /(?i)select\s+.*\s+from\s+.book.\s+.*/ + observer.invocations.get(0).parameters == [1: 1] + + + cleanup: + cleanupData() + } + + void "observer receives update"() { + given: + setupBooks() + def book = bookRepository.findAllByTitleStartingWith("Along Came a Spider").first() + def author = authorRepository.searchByName("Stephen King") + observer.clear() + + when: + bookRepository.updateAuthor(book.id, author) + + then: + observer.invocations.size() == 1 + observer.invocations[0].query =~ /(?i)update\s+.book.\s+.*/ + observer.invocations[0].parameters[1] == author.id + observer.invocations[0].parameters[3] == book.id + observer.invocations[0].affected == 1 + + cleanup: + cleanupData() + } + + void "observer receives delete"() { + given: + setupBooks() + def book = bookRepository.findAllByTitleStartingWith("Along Came a Spider").first() + observer.clear() + + when: + bookRepository.delete(book) + + then: + observer.invocations.size() == 1 + observer.invocations[0].query =~ /(?i)delete\s+from\s+.book.\s+.*/ + observer.invocations[0].parameters == [1: book.id] + + cleanup: + cleanupData() + } + + void "observer receives @Query"(){ + given: + saveSampleBooks() + observer.clear() + + when: + def book = bookDtoRepository.findByTitleWithQuery("The Stand") + + then: + book.isPresent() + book.get().title == "The Stand" + + observer.invocations.size() == 1 + observer.invocations[0].query =~ /select \* from book b where b.title = .*/ + observer.invocations[0].parameters == [1: "The Stand"] + + cleanup: + cleanupData() + } + void "test criteria functions"() { when: personRepository.deleteAll()