DATACOUCH-166 - Add basic SpEL support
Added the step of parsing SpEL expressions delimited by ${ } in StringN1qlBasedQuery, then the actual evaluation of each expression.
Refactored StringN1qlBasedQuery to use SpEL across the board, including for N1QL specific placeholders (SELECT-FROM, WHERE filter, etc...).
Refactored StringN1qlBasedQuery to use local methods rather than static methods, and amended the unit tests accordingly (using mocks).
Refactored getStatement and getCountStatement so they can see the parameters passed in at runtime.
Added unit tests + integration test.
Added SpEL support example in documentation.
This commit is contained in:
@@ -1,53 +1,120 @@
|
||||
package org.springframework.data.couchbase.repository.query;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.data.couchbase.repository.query.StringN1qlBasedQuery.*;
|
||||
|
||||
import com.couchbase.client.java.query.Statement;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
public class StringN1QlBasedQueryTest {
|
||||
|
||||
private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser();
|
||||
private StringN1qlBasedQuery mockStringUnderscoreClass;
|
||||
private StringN1qlBasedQuery mockStringAtClass;
|
||||
|
||||
@Before
|
||||
public void initMock() {
|
||||
N1qlSpelValues contextStringUnderscoreClass = StringN1qlBasedQuery.createN1qlSpelValues("B", "_class", String.class, false);
|
||||
N1qlSpelValues contextCountStringUnderscoreClass = StringN1qlBasedQuery.createN1qlSpelValues("B", "_class", String.class, true);
|
||||
N1qlSpelValues contextStringAtClass = StringN1qlBasedQuery.createN1qlSpelValues("B", "@class", String.class, false);
|
||||
N1qlSpelValues contextCountStringAtClass = StringN1qlBasedQuery.createN1qlSpelValues("B", "@class", String.class, true);
|
||||
|
||||
mockStringUnderscoreClass = mock(StringN1qlBasedQuery.class);
|
||||
when(mockStringUnderscoreClass.parseSpel(anyString(), anyBoolean(), any(Object[].class))).thenCallRealMethod();
|
||||
when(mockStringUnderscoreClass.evaluateSpelExpression(anyString(), anyBoolean(), any(Object[].class)))
|
||||
.thenAnswer(mockSpelEvaluation(contextStringUnderscoreClass, contextCountStringUnderscoreClass));
|
||||
|
||||
mockStringAtClass = mock(StringN1qlBasedQuery.class);
|
||||
when(mockStringAtClass.parseSpel(anyString(),anyBoolean(), any(Object[].class))).thenCallRealMethod();
|
||||
when(mockStringAtClass.evaluateSpelExpression(anyString(), anyBoolean(), any(Object[].class)))
|
||||
.thenAnswer(mockSpelEvaluation(contextStringAtClass, contextCountStringAtClass));
|
||||
}
|
||||
|
||||
private static Answer<?> mockSpelEvaluation(final N1qlSpelValues spelValues, final N1qlSpelValues countSpelValues) {
|
||||
return new Answer<Object>() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
EvaluationContext context = new StandardEvaluationContext();
|
||||
Expression expression = SPEL_PARSER.parseExpression((String) invocation.getArguments()[0]);
|
||||
boolean isCount = (Boolean) invocation.getArguments()[1];
|
||||
if (isCount)
|
||||
return doEvaluate(context, expression, countSpelValues);
|
||||
else
|
||||
return doEvaluate(context, expression, spelValues);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static String spel(String expression) {
|
||||
return "${" + expression + "}";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceFullSelectPlaceholderOnce() throws Exception {
|
||||
String statement = PLACEHOLDER_SELECT_FROM + " where " + PLACEHOLDER_SELECT_FROM;
|
||||
Statement parsed = StringN1qlBasedQuery.prepare(statement, "B", "_class", String.class, false);
|
||||
public void testReplaceAllFullSelectPlaceholder() throws Exception {
|
||||
String statement = spel(SPEL_SELECT_FROM_CLAUSE) + " where " + spel(SPEL_SELECT_FROM_CLAUSE);
|
||||
String parsed = mockStringUnderscoreClass.parseSpel(statement, false, new Object[0]);
|
||||
|
||||
assertEquals("SELECT META(`B`).id AS _ID, META(`B`).cas AS _CAS, `B`.* FROM `B` where "
|
||||
+ PLACEHOLDER_SELECT_FROM, parsed.toString());
|
||||
+ "SELECT META(`B`).id AS _ID, META(`B`).cas AS _CAS, `B`.* FROM `B`", parsed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceAllBucketPlaceholder() throws Exception {
|
||||
String statement = "SELECT * FROM " + PLACEHOLDER_BUCKET + " WHERE " + PLACEHOLDER_BUCKET + ".test = 1";
|
||||
Statement parsed = StringN1qlBasedQuery.prepare(statement, "B", "_class", String.class, false);
|
||||
String statement = "SELECT * FROM " + spel(SPEL_BUCKET) + " WHERE " + spel(SPEL_BUCKET) + ".test = 1";
|
||||
String parsed = mockStringUnderscoreClass.parseSpel(statement, false, new Object[0]);
|
||||
|
||||
assertEquals("SELECT * FROM `B` WHERE `B`.test = 1", parsed.toString());
|
||||
assertEquals("SELECT * FROM `B` WHERE `B`.test = 1", parsed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceFirstEntityPlaceholder() throws Exception {
|
||||
String statement = "SELECT " + PLACEHOLDER_ENTITY + " FROM b where b.test = 1 and " + PLACEHOLDER_ENTITY;
|
||||
Statement parsed = StringN1qlBasedQuery.prepare(statement, "A", "_class", String.class, false);
|
||||
public void testReplaceAllEntityPlaceholder() throws Exception {
|
||||
String statement = "SELECT " + spel(SPEL_ENTITY) + " FROM a where a.test = 1 and " + spel(SPEL_ENTITY);
|
||||
String parsed = mockStringUnderscoreClass.parseSpel(statement, false, new Object[0]);
|
||||
|
||||
assertEquals("SELECT META(`A`).id AS _ID, META(`A`).cas AS _CAS FROM b where b.test = 1 and "
|
||||
+ PLACEHOLDER_ENTITY, parsed.toString());
|
||||
assertEquals("SELECT META(`B`).id AS _ID, META(`B`).cas AS _CAS FROM a where a.test = 1 and "
|
||||
+ "META(`B`).id AS _ID, META(`B`).cas AS _CAS", parsed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceTypePlaceholder() throws Exception {
|
||||
String statement = "SELECT " + PLACEHOLDER_ENTITY + " FROM b WHERE b.test = 1 AND " + PLACEHOLDER_FILTER_TYPE;
|
||||
Statement parsed = StringN1qlBasedQuery.prepare(statement, "A", "@class", String.class, false);
|
||||
String statement = "SELECT " + spel(SPEL_ENTITY) + " FROM a WHERE a.test = 1 AND " + spel(SPEL_FILTER);
|
||||
String parsed = mockStringAtClass.parseSpel(statement, false, new Object[0]);
|
||||
|
||||
assertEquals("SELECT META(`A`).id AS _ID, META(`A`).cas AS _CAS FROM b WHERE b.test = 1 AND `@class` = "
|
||||
+ "\"java.lang.String\"", parsed.toString());
|
||||
assertEquals("SELECT META(`B`).id AS _ID, META(`B`).cas AS _CAS FROM a WHERE a.test = 1 AND `@class` = "
|
||||
+ "\"java.lang.String\"", parsed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceSelectFromPlaceholderWithCountIfCountTrue() {
|
||||
String statement = PLACEHOLDER_SELECT_FROM + " WHERE true";
|
||||
Statement parsed = StringN1qlBasedQuery.prepare(statement, "A", "_class", String.class, true);
|
||||
String statement = spel(SPEL_SELECT_FROM_CLAUSE) + " WHERE true";
|
||||
String parsed = mockStringUnderscoreClass.parseSpel(statement, true, new Object[0]);
|
||||
|
||||
assertEquals("SELECT COUNT(*) AS " + CountFragment.COUNT_ALIAS + " FROM `A` WHERE true", parsed.toString());
|
||||
assertEquals("SELECT COUNT(*) AS " + CountFragment.COUNT_ALIAS + " FROM `B` WHERE true", parsed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingOfSpel() {
|
||||
StringN1qlBasedQuery mock = mock(StringN1qlBasedQuery.class);
|
||||
when(mock.parseSpel(anyString(), anyBoolean(), any(Object[].class))).thenCallRealMethod();
|
||||
when(mock.evaluateSpelExpression(anyString(), anyBoolean(), any(Object[].class)))
|
||||
.thenReturn("EXPRESSION1", "EXPRESSION2isMuchMoreLongerThanTheTextItReplaces", "EXPRESSION3", "LASTEXPRESSION"
|
||||
);
|
||||
|
||||
String spelStatement = "some statement with ${ad{\"toto\"}.lib} spel ${expression.parsed}${{several{close}}expressions}";
|
||||
String expected = "some statement with EXPRESSION1 spel EXPRESSION2isMuchMoreLongerThanTheTextItReplacesEXPRESSION3";
|
||||
|
||||
String parsed = mock.parseSpel(spelStatement, false, new Object[0]);
|
||||
|
||||
assertEquals(expected, parsed);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
<appender-ref ref="console" />
|
||||
</root>
|
||||
|
||||
<!-- You can use these logger configurations to log more details:
|
||||
- log the Couchbase queries produced by the framework
|
||||
- log details of the parsing of SpEL in N1QL inline queries-->
|
||||
<logger name="org.springframework.data.couchbase.repository.query" level="debug"/>
|
||||
<logger name="org.springframework.data.couchbase.repository.query.StringN1qlBasedQuery" level="trace"/>
|
||||
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user