From 426e3eee714b65ec92615f04129a8047097e6d82 Mon Sep 17 00:00:00 2001 From: Evgeniy Cheban Date: Thu, 26 Dec 2024 22:46:25 +0200 Subject: [PATCH] Consider supporting SpEL in native queries Closes gh-13 --- .../StringBasedReindexerRepositoryQuery.java | 94 ++++++++++++++++--- .../repository/ReindexerRepositoryTests.java | 26 +++++ 2 files changed, 109 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/springframework/data/reindexer/repository/query/StringBasedReindexerRepositoryQuery.java b/src/main/java/org/springframework/data/reindexer/repository/query/StringBasedReindexerRepositoryQuery.java index 63c8014..56ef186 100644 --- a/src/main/java/org/springframework/data/reindexer/repository/query/StringBasedReindexerRepositoryQuery.java +++ b/src/main/java/org/springframework/data/reindexer/repository/query/StringBasedReindexerRepositoryQuery.java @@ -34,6 +34,12 @@ import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypedValue; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.util.Assert; /** @@ -43,6 +49,10 @@ */ public class StringBasedReindexerRepositoryQuery implements RepositoryQuery { + private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); + + private final NamedParameterPropertyAccessor propertyAccessor = new NamedParameterPropertyAccessor(); + private final ReindexerQueryMethod queryMethod; private final Namespace namespace; @@ -121,22 +131,50 @@ private String prepareQuery(Object[] parameters) { String value = getParameterValuePart(parameters[index - 1]); result.replace(offset + i - 1, offset + i + digits, value); offset += value.length() - digits - 1; + i += digits + 1; break; } case ':': { - StringBuilder sb = new StringBuilder(); - for (int j = i; j < queryParts.length; j++) { - if (Character.isWhitespace(queryParts[j])) { - break; + if (queryParts[i] == '#') { + int special = 1; + StringBuilder sb = new StringBuilder(); + for (int j = i + 1; j < queryParts.length; j++) { + if (queryParts[j] == '{') { + special++; + continue; + } + if (queryParts[j] == '}') { + special++; + break; + } + sb.append(queryParts[j]); + } + if (special != 3) { + throw new IllegalStateException("Invalid SpEL expression provided at index: " + i); } - sb.append(queryParts[j]); + Expression expression = this.spelExpressionParser.parseExpression(sb.toString()); + StandardEvaluationContext ctx = new StandardEvaluationContext(parameters); + ctx.addPropertyAccessor(this.propertyAccessor); + String value = getParameterValuePart(expression.getValue(ctx)); + result.replace(offset + i - 1, offset + i + expression.getExpressionString().length() + special, value); + offset += value.length() - expression.getExpressionString().length() - special - 1; + i += expression.getExpressionString().length() + special; + } else { + StringBuilder sb = new StringBuilder(); + for (int j = i; j < queryParts.length; j++) { + if (Character.isWhitespace(queryParts[j])) { + break; + } + sb.append(queryParts[j]); + } + String parameterName = sb.toString(); + Integer index = this.namedParameters.get(parameterName); + Assert.notNull(index, () -> "No parameter found for name: " + parameterName); + String value = getParameterValuePart(parameters[index]); + result.replace(offset + i - 1, offset + i + parameterName.length(), value); + offset += value.length() - parameterName.length() - 1; + i += parameterName.length() + 1; } - String parameterName = sb.toString(); - Integer index = this.namedParameters.get(parameterName); - Assert.notNull(index, () -> "No parameter found for name: " + parameterName); - String value = getParameterValuePart(parameters[index]); - result.replace(offset + i - 1, offset + i + parameterName.length(), value); - offset += value.length() - parameterName.length() - 1; break; } } @@ -197,4 +235,38 @@ public QueryMethod getQueryMethod() { return this.queryMethod; } + private final class NamedParameterPropertyAccessor implements PropertyAccessor { + + @Override + public boolean canRead(EvaluationContext context, Object target, String name) { + return StringBasedReindexerRepositoryQuery.this.namedParameters.containsKey(name); + } + + @Override + public TypedValue read(EvaluationContext context, Object target, String name) { + Assert.state(target instanceof Object[], "target must be an array"); + Object[] arguments = (Object[]) target; + Integer index = StringBasedReindexerRepositoryQuery.this.namedParameters.get(name); + Assert.notNull(index, () -> "No parameter found for name: " + name); + Object value = arguments[index]; + return new TypedValue(value); + } + + @Override + public boolean canWrite(EvaluationContext context, Object target, String name) { + return false; + } + + @Override + public void write(EvaluationContext context, Object target, String name, Object newValue) { + // NOOP + } + + @Override + public Class[] getSpecificTargetClasses() { + return new Class[0]; + } + + } + } diff --git a/src/test/java/org/springframework/data/reindexer/repository/ReindexerRepositoryTests.java b/src/test/java/org/springframework/data/reindexer/repository/ReindexerRepositoryTests.java index 1fca242..99fa781 100644 --- a/src/test/java/org/springframework/data/reindexer/repository/ReindexerRepositoryTests.java +++ b/src/test/java/org/springframework/data/reindexer/repository/ReindexerRepositoryTests.java @@ -301,6 +301,26 @@ public void findOneSqlByNameOrValueParam() { assertEquals(testItem.getValue(), item.getValue()); } + @Test + public void findOneSqlSpelByItemIdAndNameAndValueParam() { + TestItem testItem = this.repository.save(new TestItem(1L, "TestName", "TestValue")); + TestItem item = this.repository.findOneSqlSpelByItemIdAndNameAndValueParam(testItem).orElse(null); + assertNotNull(item); + assertEquals(testItem.getId(), item.getId()); + assertEquals(testItem.getName(), item.getName()); + assertEquals(testItem.getValue(), item.getValue()); + } + + @Test + public void findOneSqlSpelByIdAndNameAndValueParam() { + TestItem testItem = this.repository.save(new TestItem(1L, "TEST_NAME", "test_value")); + TestItem item = this.repository.findOneSqlSpelByIdAndNameAndValueParam(0L, "test_name", "TEST", "VALUE").orElse(null); + assertNotNull(item); + assertEquals(testItem.getId(), item.getId()); + assertEquals(testItem.getName(), item.getName()); + assertEquals(testItem.getValue(), item.getValue()); + } + @Test public void findOneSqlByIdAndNameAndValueAnyParameterOrder() { TestItem testItem = this.repository.save(new TestItem(1L, "TestName", "TestValue")); @@ -765,6 +785,12 @@ Optional findOneSqlByNameAndValueManyParams(String name1, String name2 @Query("SELECT * FROM items WHERE id = :id AND name = :name AND value = :value") Optional findOneSqlByIdAndNameAndValueParam(@Param("id") Long id, @Param("name") String name, @Param("value") String value); + @Query("SELECT * FROM items WHERE id = :#{id + 1} AND name = :#{name.toUpperCase()} AND value = :#{value1.toLowerCase() + '_' + value2.toLowerCase()}") + Optional findOneSqlSpelByIdAndNameAndValueParam(@Param("id") Long id, @Param("name") String name, @Param("value1") String value1, @Param("value2") String value2); + + @Query("SELECT * FROM items WHERE id = :#{item.id} AND name = :#{item.name} AND value = :#{item.value}") + Optional findOneSqlSpelByItemIdAndNameAndValueParam(@Param("item") TestItem item); + @Query("SELECT * FROM items WHERE id = ?2 AND name = ?3 AND value = ?1") Optional findOneSqlByIdAndNameAndValue(String value, Long id, String name);