Browse Source
SpEL expressions in queries get processed in two steps: 1. First SpEL expressions outside parameters are detected and processed. This is done with a `StandardEvaluationContext` with the variables `tableName` and `qualifiedTableName` added. This step is introduced by this commit. 2. Parameters made up by SpEL expressions are processed as usual. Closes #1856 Original pull request #1863pull/1875/head
16 changed files with 574 additions and 24 deletions
@ -0,0 +1,142 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2024 the original author or 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 org.springframework.data.jdbc.repository; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.*; |
||||||
|
import static org.mockito.Mockito.*; |
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.mockito.ArgumentCaptor; |
||||||
|
import org.springframework.context.ApplicationEventPublisher; |
||||||
|
import org.springframework.data.annotation.Id; |
||||||
|
import org.springframework.data.jdbc.core.convert.DataAccessStrategy; |
||||||
|
import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory; |
||||||
|
import org.springframework.data.jdbc.core.convert.DelegatingDataAccessStrategy; |
||||||
|
import org.springframework.data.jdbc.core.convert.JdbcConverter; |
||||||
|
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; |
||||||
|
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter; |
||||||
|
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; |
||||||
|
import org.springframework.data.jdbc.repository.query.Query; |
||||||
|
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; |
||||||
|
import org.springframework.data.relational.core.dialect.Dialect; |
||||||
|
import org.springframework.data.relational.core.dialect.HsqlDbDialect; |
||||||
|
import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
||||||
|
import org.springframework.data.relational.core.mapping.Table; |
||||||
|
import org.springframework.data.repository.CrudRepository; |
||||||
|
import org.springframework.jdbc.core.RowMapper; |
||||||
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; |
||||||
|
import org.springframework.jdbc.core.namedparam.SqlParameterSource; |
||||||
|
import org.springframework.lang.Nullable; |
||||||
|
|
||||||
|
/** |
||||||
|
* Extracts the SQL statement that results from declared queries of a repository and perform assertions on it. |
||||||
|
* |
||||||
|
* @author Jens Schauder |
||||||
|
*/ |
||||||
|
public class DeclaredQueryRepositoryUnitTests { |
||||||
|
|
||||||
|
private NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class, RETURNS_DEEP_STUBS); |
||||||
|
|
||||||
|
@Test // GH-1856
|
||||||
|
void plainSql() { |
||||||
|
|
||||||
|
repository(DummyEntityRepository.class).plainQuery(); |
||||||
|
|
||||||
|
assertThat(query()).isEqualTo("select * from someTable"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test // GH-1856
|
||||||
|
void tableNameQuery() { |
||||||
|
|
||||||
|
repository(DummyEntityRepository.class).tableNameQuery(); |
||||||
|
|
||||||
|
assertThat(query()).isEqualTo("select * from \"DUMMY_ENTITY\""); |
||||||
|
} |
||||||
|
|
||||||
|
@Test // GH-1856
|
||||||
|
void renamedTableNameQuery() { |
||||||
|
|
||||||
|
repository(RenamedEntityRepository.class).tableNameQuery(); |
||||||
|
|
||||||
|
assertThat(query()).isEqualTo("select * from \"ReNamed\""); |
||||||
|
} |
||||||
|
|
||||||
|
@Test // GH-1856
|
||||||
|
void fullyQualifiedTableNameQuery() { |
||||||
|
|
||||||
|
repository(RenamedEntityRepository.class).qualifiedTableNameQuery(); |
||||||
|
|
||||||
|
assertThat(query()).isEqualTo("select * from \"someSchema\".\"ReNamed\""); |
||||||
|
} |
||||||
|
|
||||||
|
private String query() { |
||||||
|
|
||||||
|
ArgumentCaptor<String> queryCaptor = ArgumentCaptor.forClass(String.class); |
||||||
|
verify(operations).queryForObject(queryCaptor.capture(), any(SqlParameterSource.class), any(RowMapper.class)); |
||||||
|
return queryCaptor.getValue(); |
||||||
|
} |
||||||
|
|
||||||
|
private @NotNull <T extends CrudRepository> T repository(Class<T> repositoryInterface) { |
||||||
|
|
||||||
|
Dialect dialect = HsqlDbDialect.INSTANCE; |
||||||
|
|
||||||
|
RelationalMappingContext context = new JdbcMappingContext(); |
||||||
|
|
||||||
|
DelegatingDataAccessStrategy delegatingDataAccessStrategy = new DelegatingDataAccessStrategy(); |
||||||
|
JdbcConverter converter = new MappingJdbcConverter(context, delegatingDataAccessStrategy, |
||||||
|
new JdbcCustomConversions(), new DefaultJdbcTypeFactory(operations.getJdbcOperations())); |
||||||
|
|
||||||
|
DataAccessStrategy dataAccessStrategy = mock(DataAccessStrategy.class); |
||||||
|
ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); |
||||||
|
|
||||||
|
JdbcRepositoryFactory factory = new JdbcRepositoryFactory(dataAccessStrategy, context, converter, dialect, |
||||||
|
publisher, operations); |
||||||
|
|
||||||
|
return factory.getRepository(repositoryInterface); |
||||||
|
} |
||||||
|
|
||||||
|
@Table |
||||||
|
record DummyEntity(@Id Long id, String name) { |
||||||
|
} |
||||||
|
|
||||||
|
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> { |
||||||
|
|
||||||
|
@Nullable |
||||||
|
@Query("select * from someTable") |
||||||
|
DummyEntity plainQuery(); |
||||||
|
|
||||||
|
@Nullable |
||||||
|
@Query("select * from #{#tableName}") |
||||||
|
DummyEntity tableNameQuery(); |
||||||
|
} |
||||||
|
|
||||||
|
@Table(name = "ReNamed", schema = "someSchema") |
||||||
|
record RenamedEntity(@Id Long id, String name) { |
||||||
|
} |
||||||
|
|
||||||
|
interface RenamedEntityRepository extends CrudRepository<RenamedEntity, Long> { |
||||||
|
|
||||||
|
@Nullable |
||||||
|
@Query("select * from #{#tableName}") |
||||||
|
DummyEntity tableNameQuery(); |
||||||
|
|
||||||
|
@Nullable |
||||||
|
@Query("select * from #{#qualifiedTableName}") |
||||||
|
DummyEntity qualifiedTableNameQuery(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,95 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2018-2024 the original author or 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 org.springframework.data.r2dbc.repository.support; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.*; |
||||||
|
|
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
import java.time.Duration; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.junit.jupiter.api.extension.ExtendWith; |
||||||
|
import org.mockito.junit.jupiter.MockitoExtension; |
||||||
|
import org.springframework.data.annotation.Id; |
||||||
|
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter; |
||||||
|
import org.springframework.data.r2dbc.convert.R2dbcConverter; |
||||||
|
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy; |
||||||
|
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; |
||||||
|
import org.springframework.data.r2dbc.dialect.H2Dialect; |
||||||
|
import org.springframework.data.r2dbc.dialect.PostgresDialect; |
||||||
|
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; |
||||||
|
import org.springframework.data.r2dbc.repository.Query; |
||||||
|
import org.springframework.data.r2dbc.testing.StatementRecorder; |
||||||
|
import org.springframework.data.repository.Repository; |
||||||
|
import org.springframework.r2dbc.core.DatabaseClient; |
||||||
|
|
||||||
|
/** |
||||||
|
* Test extracting the SQL from a repository method call and performing assertions on it. |
||||||
|
* |
||||||
|
* @author Jens Schauder |
||||||
|
*/ |
||||||
|
@ExtendWith(MockitoExtension.class) |
||||||
|
public class SqlInspectingR2dbcRepositoryUnitTests { |
||||||
|
|
||||||
|
R2dbcConverter r2dbcConverter = new MappingR2dbcConverter(new R2dbcMappingContext()); |
||||||
|
|
||||||
|
DatabaseClient databaseClient; |
||||||
|
StatementRecorder recorder = StatementRecorder.newInstance(); |
||||||
|
ReactiveDataAccessStrategy dataAccessStrategy = new DefaultReactiveDataAccessStrategy(H2Dialect.INSTANCE); |
||||||
|
|
||||||
|
|
||||||
|
@BeforeEach |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public void before() { |
||||||
|
|
||||||
|
databaseClient = DatabaseClient.builder().connectionFactory(recorder) |
||||||
|
.bindMarkers(H2Dialect.INSTANCE.getBindMarkersFactory()).build(); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Test // GH-1856
|
||||||
|
public void replacesSpelExpressionInQuery() { |
||||||
|
|
||||||
|
recorder.addStubbing(SqlInspectingR2dbcRepositoryUnitTests::isSelect, List.of()); |
||||||
|
|
||||||
|
R2dbcRepositoryFactory factory = new R2dbcRepositoryFactory(databaseClient, dataAccessStrategy); |
||||||
|
MyPersonRepository repository = factory.getRepository(MyPersonRepository.class); |
||||||
|
|
||||||
|
assertThat(repository).isNotNull(); |
||||||
|
|
||||||
|
repository.findBySpel().block(Duration.ofMillis(100)); |
||||||
|
|
||||||
|
StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(SqlInspectingR2dbcRepositoryUnitTests::isSelect); |
||||||
|
|
||||||
|
assertThat(statement.getSql()).isEqualTo("select * from PERSONx"); |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean isSelect(String sql) { |
||||||
|
return sql.toLowerCase().startsWith("select"); |
||||||
|
} |
||||||
|
|
||||||
|
interface MyPersonRepository extends Repository<Person, Long> { |
||||||
|
@Query("select * from #{#tableName +'x'}") |
||||||
|
Mono<Person> findBySpel(); |
||||||
|
} |
||||||
|
|
||||||
|
static class Person { |
||||||
|
@Id long id; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2024 the original author or 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 org.springframework.data.relational.repository.query; |
||||||
|
|
||||||
|
public interface QueryPreprocessor { |
||||||
|
|
||||||
|
QueryPreprocessor NOOP = new QueryPreprocessor() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public String transform(String query) { |
||||||
|
return query; |
||||||
|
} |
||||||
|
}; |
||||||
|
String transform(String query); |
||||||
|
} |
||||||
@ -0,0 +1,64 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2024 the original author or 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 org.springframework.data.relational.repository.support; |
||||||
|
|
||||||
|
import org.springframework.data.mapping.context.MappingContext; |
||||||
|
import org.springframework.data.relational.core.dialect.Dialect; |
||||||
|
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; |
||||||
|
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; |
||||||
|
import org.springframework.data.relational.core.sql.SqlIdentifier; |
||||||
|
import org.springframework.data.relational.repository.query.QueryPreprocessor; |
||||||
|
import org.springframework.data.repository.core.RepositoryMetadata; |
||||||
|
import org.springframework.data.repository.query.QueryLookupStrategy; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
/** |
||||||
|
* Base class for R2DBC and JDBC {@link QueryLookupStrategy} implementations. |
||||||
|
* |
||||||
|
* @author Jens Schauder |
||||||
|
* @since 3.4 |
||||||
|
*/ |
||||||
|
public abstract class RelationalQueryLookupStrategy implements QueryLookupStrategy { |
||||||
|
|
||||||
|
private final MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context; |
||||||
|
private final Dialect dialect; |
||||||
|
|
||||||
|
protected RelationalQueryLookupStrategy( |
||||||
|
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context, |
||||||
|
Dialect dialect) { |
||||||
|
|
||||||
|
Assert.notNull(context, "RelationalMappingContext must not be null"); |
||||||
|
Assert.notNull(dialect, "Dialect must not be null"); |
||||||
|
|
||||||
|
this.context = context; |
||||||
|
this.dialect = dialect; |
||||||
|
} |
||||||
|
|
||||||
|
protected String evaluateTableExpressions(RepositoryMetadata repositoryMetadata, String queryString) { |
||||||
|
|
||||||
|
return prepareQueryPreprocessor(repositoryMetadata).transform(queryString); |
||||||
|
} |
||||||
|
|
||||||
|
private QueryPreprocessor prepareQueryPreprocessor(RepositoryMetadata repositoryMetadata) { |
||||||
|
|
||||||
|
SqlIdentifier tableName = context.getPersistentEntity(repositoryMetadata.getDomainType()).getTableName(); |
||||||
|
SqlIdentifier qualifiedTableName = context.getPersistentEntity(repositoryMetadata.getDomainType()) |
||||||
|
.getQualifiedTableName(); |
||||||
|
return new TableNameQueryPreprocessor(tableName, qualifiedTableName, dialect); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,80 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2024 the original author or 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 org.springframework.data.relational.repository.support; |
||||||
|
|
||||||
|
import org.springframework.data.relational.core.dialect.Dialect; |
||||||
|
import org.springframework.data.relational.core.sql.SqlIdentifier; |
||||||
|
import org.springframework.data.relational.repository.query.QueryPreprocessor; |
||||||
|
import org.springframework.expression.Expression; |
||||||
|
import org.springframework.expression.ParserContext; |
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser; |
||||||
|
import org.springframework.expression.spel.support.StandardEvaluationContext; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
/** |
||||||
|
* Replaces SpEL expressions based on table names in query strings. |
||||||
|
* |
||||||
|
* @author Jens Schauder |
||||||
|
*/ |
||||||
|
class TableNameQueryPreprocessor implements QueryPreprocessor { |
||||||
|
|
||||||
|
private static final String EXPRESSION_PARAMETER = "$1#{"; |
||||||
|
private static final String QUOTED_EXPRESSION_PARAMETER = "$1__HASH__{"; |
||||||
|
|
||||||
|
private static final Pattern EXPRESSION_PARAMETER_QUOTING = Pattern.compile("([:?])#\\{"); |
||||||
|
private static final Pattern EXPRESSION_PARAMETER_UNQUOTING = Pattern.compile("([:?])__HASH__\\{"); |
||||||
|
|
||||||
|
private final SqlIdentifier tableName; |
||||||
|
private final SqlIdentifier qualifiedTableName; |
||||||
|
private final Dialect dialect; |
||||||
|
|
||||||
|
public TableNameQueryPreprocessor(SqlIdentifier tableName, SqlIdentifier qualifiedTableName, Dialect dialect) { |
||||||
|
|
||||||
|
Assert.notNull(tableName, "TableName must not be null"); |
||||||
|
Assert.notNull(qualifiedTableName, "QualifiedTableName must not be null"); |
||||||
|
Assert.notNull(dialect, "Dialect must not be null"); |
||||||
|
|
||||||
|
this.tableName = tableName; |
||||||
|
this.qualifiedTableName = qualifiedTableName; |
||||||
|
this.dialect = dialect; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String transform(String query) { |
||||||
|
|
||||||
|
StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); |
||||||
|
evaluationContext.setVariable("tableName", tableName.toSql(dialect.getIdentifierProcessing())); |
||||||
|
evaluationContext.setVariable("qualifiedTableName", qualifiedTableName.toSql(dialect.getIdentifierProcessing())); |
||||||
|
|
||||||
|
SpelExpressionParser parser = new SpelExpressionParser(); |
||||||
|
|
||||||
|
query = quoteExpressionsParameter(query); |
||||||
|
Expression expression = parser.parseExpression(query, ParserContext.TEMPLATE_EXPRESSION); |
||||||
|
|
||||||
|
return unquoteParameterExpressions(expression.getValue(evaluationContext, String.class)); |
||||||
|
} |
||||||
|
|
||||||
|
private static String unquoteParameterExpressions(String result) { |
||||||
|
return EXPRESSION_PARAMETER_UNQUOTING.matcher(result).replaceAll(EXPRESSION_PARAMETER); |
||||||
|
} |
||||||
|
|
||||||
|
private static String quoteExpressionsParameter(String query) { |
||||||
|
return EXPRESSION_PARAMETER_QUOTING.matcher(query).replaceAll(QUOTED_EXPRESSION_PARAMETER); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,46 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2024 the original author or 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 org.springframework.data.relational.repository.support; |
||||||
|
|
||||||
|
import org.assertj.core.api.SoftAssertions; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.springframework.data.relational.core.dialect.AnsiDialect; |
||||||
|
import org.springframework.data.relational.core.sql.SqlIdentifier; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link TableNameQueryPreprocessor}. |
||||||
|
* |
||||||
|
* @author Jens Schauder |
||||||
|
*/ |
||||||
|
class TableNameQueryPreprocessorUnitTests { |
||||||
|
|
||||||
|
@Test // GH-1856
|
||||||
|
void transform() { |
||||||
|
|
||||||
|
TableNameQueryPreprocessor preprocessor = new TableNameQueryPreprocessor(SqlIdentifier.quoted("some_table_name"), SqlIdentifier.quoted("qualified_table_name"), AnsiDialect.INSTANCE); |
||||||
|
SoftAssertions.assertSoftly(softly -> { |
||||||
|
|
||||||
|
softly.assertThat(preprocessor.transform("someString")).isEqualTo("someString"); |
||||||
|
softly.assertThat(preprocessor.transform("someString#{#tableName}restOfString")) |
||||||
|
.isEqualTo("someString\"some_table_name\"restOfString"); |
||||||
|
softly.assertThat(preprocessor.transform("select from #{#tableName} where x = :#{#some other spel}")) |
||||||
|
.isEqualTo("select from \"some_table_name\" where x = :#{#some other spel}"); |
||||||
|
softly.assertThat(preprocessor.transform("select from #{#qualifiedTableName}")) |
||||||
|
.isEqualTo("select from \"qualified_table_name\""); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue