SRP closePipeline should return List of tx results

and strip individual op results

DATAREDIS-223

- closePipeline now returns Lists of exec results instead of flattening results

- Temp revert conversion of exec results for
consistency
This commit is contained in:
Jennifer Hickey
2013-07-19 12:06:34 -07:00
parent a0b4613a4b
commit a40a88795a
7 changed files with 534 additions and 496 deletions

View File

@@ -76,6 +76,7 @@ public class SrpConnection implements RedisConnection {
private boolean pipelineRequested = false;
private Pipeline pipeline;
private PipelineTracker callback;
private PipelineTracker txTracker;
private volatile SrpSubscription subscription;
private boolean convertPipelineResults=true;
@@ -84,7 +85,12 @@ public class SrpConnection implements RedisConnection {
private class PipelineTracker implements FutureCallback<Reply> {
private final List<Object> results = Collections.synchronizedList(new ArrayList<Object>());
private final Queue<SrpGenericResult> futureResults = new LinkedList<SrpGenericResult>();
private final Queue<FutureResult> futureResults = new LinkedList<FutureResult>();
private boolean convertResults;
public PipelineTracker(boolean convertResults) {
this.convertResults = convertResults;
}
public void onSuccess(Reply result) {
results.add(result.data());
@@ -94,35 +100,56 @@ public class SrpConnection implements RedisConnection {
results.add(t);
}
@SuppressWarnings("unchecked")
public List<Object> complete() {
int txResults = 0;
List<ListenableFuture<? extends Reply>> futures = new ArrayList<ListenableFuture<? extends Reply>>();
for(SrpGenericResult future: futureResults) {
futures.add(future.getResultHolder());
for(FutureResult future: futureResults) {
if(future instanceof SrpTxResult) {
txResults++;
} else {
ListenableFuture<? extends Reply> f = (ListenableFuture<? extends Reply>) future.getResultHolder();
futures.add(f);
}
}
try {
Futures.successfulAsList(futures).get();
} catch (Exception ex) {
// ignore
}
if(futureResults.size() != results.size()) {
if(futureResults.size() != results.size() + txResults) {
throw new RedisPipelineException("Received a different number of results than expected. Expected: " +
futureResults.size(), results);
}
List<Object> convertedResults = new ArrayList<Object>();
for(Object result: results) {
SrpGenericResult futureResult = futureResults.remove();
if(result instanceof Exception || !convertPipelineResults) {
convertedResults.add(result);
}else if(!(futureResult instanceof SrpStatusResult)) {
convertedResults.add(futureResult.convert(result));
int i = 0;
for(FutureResult future: futureResults) {
if(future instanceof SrpTxResult) {
PipelineTracker txTracker = ((SrpTxResult)future).getResultHolder();
if(txTracker != null) {
convertedResults.add(getPipelinedResults(txTracker, true));
}else {
convertedResults.add(null);
}
}else {
Object result = results.get(i);
if(result instanceof Exception || !convertResults) {
convertedResults.add(result);
}else if(!(future instanceof SrpStatusResult)) {
convertedResults.add(future.convert(result));
}
i++;
}
}
return convertedResults;
}
public void addCommand(SrpGenericResult result) {
public void addCommand(FutureResult result) {
futureResults.add(result);
Futures.addCallback(result.getResultHolder(), this);
if(!(result instanceof SrpTxResult)) {
Futures.addCallback(((SrpGenericResult)result).getResultHolder(), this);
}
}
public void close() {
@@ -164,6 +191,18 @@ public class SrpConnection implements RedisConnection {
}
}
private class SrpTxResult extends FutureResult<PipelineTracker> {
public SrpTxResult(PipelineTracker txTracker) {
super(txTracker);
}
public List<Object> get() {
if(resultHolder == null) {
return null;
}
return resultHolder.complete();
}
}
public SrpConnection(String host, int port, BlockingQueue<SrpConnection> queue) {
try {
this.client = new RedisClient(host, port);
@@ -224,7 +263,7 @@ public class SrpConnection implements RedisConnection {
}
public boolean isPipelined() {
return (pipeline != null);
return pipelineRequested || (txTracker != null);
}
@@ -233,10 +272,6 @@ public class SrpConnection implements RedisConnection {
initPipeline();
}
public List<Object> closePipeline() {
return closePipeline(true);
}
public List<byte[]> sort(byte[] key, SortParameters params) {
Object[] sort = sortParams(params);
@@ -481,10 +516,9 @@ public class SrpConnection implements RedisConnection {
public void discard() {
isMulti = false;
try {
// discard tracked futures
txTracker = null;
client.discard();
if (!pipelineRequested) {
closePipeline(false);
}
} catch (Exception ex) {
throw convertSrpAccessException(ex);
}
@@ -493,33 +527,27 @@ public class SrpConnection implements RedisConnection {
public List<Object> exec() {
isMulti = false;
Exception execException = null;
List<Object> results = null;
boolean resultsSet = true;
try {
Future<Boolean> exec = client.exec();
// Need to wait on execution or subsequent non-pipelined calls may read exec results
resultsSet = exec.get();
} catch (Exception ex) {
execException = ex;
} finally {
// ensure pipeline is closed even if error in exec
if (!pipelineRequested) {
try {
results = closePipeline();
} catch (RedisPipelineException e) {
if(execException == null) {
execException = e;
}
boolean resultsSet = exec.get();
if(!resultsSet) {
// This is the case where a nil MultiBulk Reply was returned b/c watched variable modified
if(pipelineRequested) {
pipeline(new SrpTxResult(null));
}
return null;
}
if(pipelineRequested) {
pipeline(new SrpTxResult(txTracker));
return null;
}
return closeTransaction();
} catch (Exception ex) {
throw convertSrpAccessException(ex);
} finally {
txTracker = null;
}
// If resultsSet is false, it's b/c of nil Multi-Bulk reply (watch case) and null is returned
if(execException != null && resultsSet) {
// Intentionally convert RedisPipelineException too, it's an impl detail
throw convertSrpAccessException(execException);
}
return results;
}
@@ -603,7 +631,7 @@ public class SrpConnection implements RedisConnection {
return;
}
isMulti = true;
initPipeline();
initTxTracker();
try {
client.multi();
} catch (Exception ex) {
@@ -2168,33 +2196,52 @@ public class SrpConnection implements RedisConnection {
}
// processing method that adds a listener to the future in order to track down the results and close the pipeline
private void pipeline(SrpGenericResult future) {
callback.addCommand(future);
private void pipeline(FutureResult<?> future) {
if(isQueueing()) {
txTracker.addCommand(future);
}else {
callback.addCommand(future);
}
}
private void initPipeline() {
if (pipeline == null) {
callback = new PipelineTracker();
callback = new PipelineTracker(convertPipelineResults);
pipeline = client.pipeline();
}
}
private List<Object> closePipeline(boolean getResults) {
private void initTxTracker() {
if(txTracker == null) {
txTracker = new PipelineTracker(false);
}
initPipeline();
}
public List<Object> closePipeline() {
pipelineRequested = false;
List<Object> results = Collections.emptyList();
if (pipeline != null) {
pipeline = null;
if(getResults) {
results = getPipelinedResults();
}
results = getPipelinedResults(callback, true);
callback.close();
callback = null;
}
return results;
}
private List<Object> getPipelinedResults() {
List<Object> execute = new ArrayList<Object>(callback.complete());
private List<Object> closeTransaction() {
List<Object> results = Collections.emptyList();
if (txTracker != null) {
results = getPipelinedResults(txTracker, false);
txTracker.close();
txTracker = null;
}
return results;
}
private List<Object> getPipelinedResults(PipelineTracker tracker, boolean throwPipelineException) {
List<Object> execute = new ArrayList<Object>(tracker.complete());
if (execute != null && !execute.isEmpty()) {
Exception cause = null;
for (int i = 0; i < execute.size(); i++) {
@@ -2208,7 +2255,11 @@ public class SrpConnection implements RedisConnection {
}
}
if (cause != null) {
throw new RedisPipelineException(cause, execute);
if(throwPipelineException) {
throw new RedisPipelineException(cause, execute);
}else {
throw convertSrpAccessException(cause);
}
}
return execute;

View File

@@ -1,6 +1,7 @@
package org.springframework.data.redis.connection;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.util.ArrayList;
import java.util.Arrays;
@@ -344,9 +345,22 @@ abstract public class AbstractConnectionTransactionIntegrationTests extends
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testDumpAndRestore() {
convert = false;
super.testDumpAndRestore();
connection.set("testing", "12");
actual.add(connection.dump("testing".getBytes()));
List<Object> results = getResults();
initConnection();
actual.add(connection.del("testing"));
actual.add((connection.get("testing")));
connection.restore("testing".getBytes(), 0, (byte[]) results.get(results.size() - 1));
actual.add(connection.get("testing"));
results = getResults();
assertEquals(3,results.size());
assertEquals(1l, results.get(0));
assertNull(results.get(1));
assertEquals("12", new String((byte[])results.get(2)));
}
@Test
@@ -383,6 +397,24 @@ abstract public class AbstractConnectionTransactionIntegrationTests extends
super.testEvalReturnArrayStrings();
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testEvalReturnSingleOK() {
actual.add(connection.eval("return redis.call('set','abc','ghk')", ReturnType.STATUS, 0));
assertEquals(Arrays.asList(new Object[] { "OK" }), getResultsNoConversion());
}
@SuppressWarnings("unchecked")
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testEvalReturnArrayOKs() {
actual.add(connection.eval(
"return { redis.call('set','abc','ghk'), redis.call('set','abc','lfdf')}",
ReturnType.MULTI, 0));
List<String> result = (List<String>) getResults().get(0);
assertEquals(Arrays.asList(new Object[] { "OK", "OK" }), result);
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testRestoreTtl() {
@@ -398,6 +430,57 @@ abstract public class AbstractConnectionTransactionIntegrationTests extends
connection.scriptKill();
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testBitCount() {
convertLongToBoolean = false;
String key = "bitset-test";
connection.setBit(key, 0, false);
connection.setBit(key, 1, true);
connection.setBit(key, 2, true);
actual.add(connection.bitCount(key));
// Lettuce setBit returns Long instead of void
verifyResults(new ArrayList<Object>(Arrays.asList(0l, 0l, 0l, 2l)));
}
@Test
public void testGetRangeSetRange() {
connection.set("rangekey", "supercalifrag");
actual.add(connection.getRange("rangekey", 0l, 2l));
connection.setRange("rangekey", "ck", 2);
actual.add(connection.get("rangekey"));
// Lettuce returns a value for setRange
verifyResults(Arrays.asList(new Object[] { "sup", 13l, "suckrcalifrag" }));
}
@SuppressWarnings("unchecked")
@Test
public void testZRevRangeByScoreOffsetCount() {
actual.add(byteConnection.zAdd("myset".getBytes(), 2, "Bob".getBytes()));
actual.add(byteConnection.zAdd("myset".getBytes(), 1, "James".getBytes()));
actual.add(byteConnection.zRevRangeByScore("myset".getBytes(), 0d, 3d, 0, 5));
assertEquals(Arrays.asList(new String[] { "Bob", "James" }),(List<String>) getResults().get(2));
}
@SuppressWarnings("unchecked")
@Test
public void testZRevRangeByScore() {
actual.add(byteConnection.zAdd("myset".getBytes(), 2, "Bob".getBytes()));
actual.add(byteConnection.zAdd("myset".getBytes(), 1, "James".getBytes()));
actual.add(byteConnection.zRevRangeByScore("myset".getBytes(), 0d, 3d));
assertEquals(Arrays.asList(new String[] { "Bob", "James" }), (List<String>) getResults().get(2));
}
@SuppressWarnings("unchecked")
@Test
public void testZRevRangeByScoreWithScoresOffsetCount() {
actual.add(byteConnection.zAdd("myset".getBytes(), 2, "Bob".getBytes()));
actual.add(byteConnection.zAdd("myset".getBytes(), 1, "James".getBytes()));
actual.add(byteConnection.zRevRangeByScore("myset".getBytes(), 0d, 3d, 0, 5));
assertEquals(Arrays.asList(new String[] { "Bob", "James" }),
(List<byte[]>) getResults().get(2));
}
protected void initConnection() {
connection.multi();
}
@@ -442,6 +525,9 @@ abstract public class AbstractConnectionTransactionIntegrationTests extends
&& ((List) result).get(0) instanceof byte[]) {
return (SerializationUtils.deserialize((List<byte[]>) result, stringSerializer));
} else if (result instanceof byte[]) {
if(convertStringToProps) {
return Converters.toProperties(stringSerializer.deserialize((byte[]) result));
}
return (stringSerializer.deserialize((byte[]) result));
} else if (result instanceof Map
&& ((Map) result).keySet().iterator().next() instanceof byte[]) {

View File

@@ -16,7 +16,6 @@
package org.springframework.data.redis.connection.lettuce;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.util.ArrayList;
import java.util.Arrays;
@@ -32,9 +31,8 @@ import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.AbstractConnectionTransactionIntegrationTests;
import org.springframework.data.redis.connection.DefaultStringRedisConnection;
import org.springframework.data.redis.connection.DefaultStringTuple;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.connection.StringRedisConnection.StringTuple;
import org.springframework.data.redis.connection.convert.SetConverter;
import org.springframework.test.annotation.IfProfileValue;
@@ -174,19 +172,6 @@ public class LettuceConnectionTransactionIntegrationTests extends
verifyResults(Arrays.asList(new Object[] { 0l, 0l, 0l, 1l }));
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testBitCount() {
convertLongToBoolean = false;
String key = "bitset-test";
connection.setBit(key, 0, false);
connection.setBit(key, 1, true);
connection.setBit(key, 2, true);
actual.add(connection.bitCount(key));
// Lettuce setBit returns Long instead of void
verifyResults(new ArrayList<Object>(Arrays.asList(0l, 0l, 0l, 2l)));
}
@Test
public void testHKeys() {
convertListToSet = true;
@@ -198,16 +183,6 @@ public class LettuceConnectionTransactionIntegrationTests extends
super.testBitOpNotMultipleSources();
}
@Test
public void testGetRangeSetRange() {
connection.set("rangekey", "supercalifrag");
actual.add(connection.getRange("rangekey", 0l, 2l));
connection.setRange("rangekey", "ck", 2);
actual.add(connection.get("rangekey"));
// Lettuce returns a value for setRange
verifyResults(Arrays.asList(new Object[] { "sup", 13l, "suckrcalifrag" }));
}
@Test(expected = UnsupportedOperationException.class)
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testSRandMemberCountNegative() {
@@ -220,77 +195,12 @@ public class LettuceConnectionTransactionIntegrationTests extends
super.testSortStoreNullParams();
}
@SuppressWarnings("unchecked")
@Test
public void testZRevRangeByScoreOffsetCount() {
actual.add(byteConnection.zAdd("myset".getBytes(), 2, "Bob".getBytes()));
actual.add(byteConnection.zAdd("myset".getBytes(), 1, "James".getBytes()));
actual.add(byteConnection.zRevRangeByScore("myset".getBytes(), 0d, 3d, 0, 5));
assertEquals(Arrays.asList(new String[] { "Bob", "James" }),(List<String>) getResults().get(2));
}
@SuppressWarnings("unchecked")
@Test
public void testZRevRangeByScore() {
actual.add(byteConnection.zAdd("myset".getBytes(), 2, "Bob".getBytes()));
actual.add(byteConnection.zAdd("myset".getBytes(), 1, "James".getBytes()));
actual.add(byteConnection.zRevRangeByScore("myset".getBytes(), 0d, 3d));
assertEquals(Arrays.asList(new String[] { "Bob", "James" }), (List<String>) getResults().get(2));
}
@SuppressWarnings("unchecked")
@Test
public void testZRevRangeByScoreWithScoresOffsetCount() {
actual.add(byteConnection.zAdd("myset".getBytes(), 2, "Bob".getBytes()));
actual.add(byteConnection.zAdd("myset".getBytes(), 1, "James".getBytes()));
actual.add(byteConnection.zRevRangeByScore("myset".getBytes(), 0d, 3d, 0, 5));
assertEquals(Arrays.asList(new String[] { "Bob", "James" }),
(List<byte[]>) getResults().get(2));
}
@Test
public void testZRevRangeByScoreWithScores() {
convertToStringTuple = false;
super.testZRevRangeByScoreWithScores();
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testEvalReturnSingleOK() {
actual.add(connection.eval("return redis.call('set','abc','ghk')", ReturnType.STATUS, 0));
assertEquals(Arrays.asList(new Object[] { "OK" }), getResultsNoConversion());
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testDumpAndRestore() {
convert = false;
connection.set("testing", "12");
actual.add(connection.dump("testing".getBytes()));
List<Object> results = getResults();
initConnection();
actual.add(connection.del("testing"));
actual.add((connection.get("testing")));
connection.restore("testing".getBytes(), 0, (byte[]) results.get(results.size() - 1));
actual.add(connection.get("testing"));
results = getResults();
assertEquals(3,results.size());
assertEquals(1l, results.get(0));
assertNull(results.get(1));
assertEquals("12", new String((byte[])results.get(2)));
}
@SuppressWarnings("unchecked")
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testEvalReturnArrayOKs() {
actual.add(connection.eval(
"return { redis.call('set','abc','ghk'), redis.call('set','abc','lfdf')}",
ReturnType.MULTI, 0));
List<String> result = (List<String>) getResults().get(0);
assertEquals(Arrays.asList(new Object[] { "OK", "OK" }), result);
}
@Test
public void testMove() {
connection.set("foo", "bar");

View File

@@ -16,17 +16,12 @@
package org.springframework.data.redis.connection.srp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.util.Arrays;
import java.util.List;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.data.redis.connection.AbstractConnectionIntegrationTests;
import org.springframework.data.redis.connection.DefaultStringRedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.context.ContextConfiguration;
@@ -88,41 +83,4 @@ public class SrpConnectionIntegrationTests extends AbstractConnectionIntegration
ReturnType.MULTI, 0));
verifyResults(Arrays.asList(new Object[] {Arrays.asList(new Object[] {"OK", "OK"} )}));
}
@SuppressWarnings("unchecked")
@Test
public void testMultiExec() throws Exception {
connection.multi();
connection.set("key", "value");
actual.add(connection.get("key"));
actual.add(connection.exec());
List<Object> results = getResults();
assertNull(results.get(0));
List<Object> execResults = (List<Object>) results.get(1);
// SRP is already filtering out the void types since tx uses pipeline, so result size here is only 1
assertEquals(1, execResults.size());
// However, since DefaultStringRedisConnection doesn't yet convert tx results, the data is still in raw bytes
assertEquals("value", new String((byte[]) execResults.get(0)));
assertEquals("value", connection.get("key"));
}
@SuppressWarnings("unchecked")
@Test
public void testUnwatch() throws Exception {
connection.set("testitnow", "willdo");
connection.watch("testitnow".getBytes());
connection.unwatch();
connection.multi();
DefaultStringRedisConnection conn2 = new DefaultStringRedisConnection(
connectionFactory.getConnection());
conn2.set("testitnow", "something");
connection.set("testitnow", "somethingelse");
actual.add(connection.get("testitnow"));
actual.add(connection.exec());
List<Object> results = getResults();
assertNull(results.get(0));
List<Object> execResults = (List<Object>) results.get(1);
// SRP is already filtering out the void types since tx uses pipeline, so result size here is only 1
assertEquals("somethingelse", new String((byte[]) execResults.get(0)));
}
}

View File

@@ -15,18 +15,12 @@
*/
package org.springframework.data.redis.connection.srp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.AbstractConnectionPipelineIntegrationTests;
import org.springframework.data.redis.connection.DefaultStringRedisConnection;
import org.springframework.data.redis.connection.RedisPipelineException;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.context.ContextConfiguration;
@@ -43,67 +37,6 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
public class SrpConnectionPipelineIntegrationTests extends
AbstractConnectionPipelineIntegrationTests {
@Test
public void testMultiDiscard() throws Exception {
DefaultStringRedisConnection conn2 = new DefaultStringRedisConnection(
connectionFactory.getConnection());
conn2.set("testitnow", "willdo");
connection.multi();
connection.set("testitnow2", "notok");
connection.discard();
// SRP throws Exception on transaction discard, so we expect pipeline
// exception here
try {
getResults();
fail("Closing the pipeline on a discarded tx should throw a RedisPipelineException");
} catch (RedisPipelineException e) {
}
assertEquals("willdo", connection.get("testitnow"));
connection.openPipeline();
// Ensure we can run a new tx after discarding previous one
testMultiExec();
}
@Test
public void testMultiExec() throws Exception {
connection.multi();
connection.set("key", "value");
connection.get("key");
actual.add(connection.exec());
List<Object> results = getResults();
// For the moment, SRP exec results are flattened into pipeline
assertEquals(1, results.size());
assertEquals("value", new String((byte[]) results.get(0)));
assertEquals("value", connection.get("key"));
}
@Test
public void testUnwatch() throws Exception {
connection.set("testitnow", "willdo");
connection.watch("testitnow".getBytes());
connection.unwatch();
connection.multi();
//Give some time for unwatch to be asynch executed
Thread.sleep(500);
DefaultStringRedisConnection conn2 = new DefaultStringRedisConnection(
connectionFactory.getConnection());
conn2.set("testitnow", "something");
connection.set("testitnow", "somethingelse");
connection.get("testitnow");
actual.add(connection.exec());
List<Object> results = getResults();
// For the moment, SRP exec results are flattened into pipeline
assertEquals("somethingelse", new String((byte[]) results.get(0)));
}
// SRP sets results of all commands in the pipeline to RedisException if
// exec returns a
// null multi-bulk reply
@Test(expected = RedisPipelineException.class)
public void testWatch() throws Exception {
super.testWatch();
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testEvalReturnArrayOKs() {

View File

@@ -17,14 +17,12 @@ package org.springframework.data.redis.connection.srp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.RedisVersionUtils;
import org.springframework.data.redis.connection.RedisPipelineException;
import org.springframework.test.annotation.IfProfileValue;
@@ -36,30 +34,6 @@ import org.springframework.test.annotation.IfProfileValue;
*/
public class SrpConnectionPipelineTxIntegrationTests extends SrpConnectionTransactionIntegrationTests {
@Test
public void exceptionExecuteNative() throws Exception {
connection.execute("ZadD", getClass() + "#foo\t0.90\titem");
try {
getResults();
fail("Expected an Exception to be thrown executing a command with syntax error");
}catch(RedisPipelineException e) {
// Redis 2.4, Exception occurs when we get the result of execute on closePipeline
if(RedisVersionUtils.atLeast("2.6.4", byteConnection)) {
fail("RedisPipelineException should not be thrown in Redis 2.6");
}
}catch(RedisSystemException e) {
try {
connection.closePipeline();
}catch(Exception ex) {
// Gonna get another error on closing the pipeline as results from execute
}
if(!RedisVersionUtils.atLeast("2.6", byteConnection)) {
// Redis 2.6 returns an ErrorReploy on exec, the Exception occurs when we call exec()
// b/c it waits on the response
fail("RedisSystemException should only be thrown in Redis 2.6");
}
}
}
@Test
public void testUsePipelineAfterTxExec() {
@@ -67,11 +41,11 @@ public class SrpConnectionPipelineTxIntegrationTests extends SrpConnectionTransa
assertNull(connection.exec());
assertNull(connection.get("foo"));
List<Object> results = connection.closePipeline();
assertEquals(1, results.size());
assertEquals("bar", new String((byte[]) results.get(0)));
assertEquals(Arrays.asList(new Object[] {Collections.singletonList("OK"), "bar"}), results);
assertEquals("bar", connection.get("foo"));
}
@SuppressWarnings("unchecked")
@Test
public void testExec2TransactionsInPipeline() {
connection.set("foo", "bar");
@@ -83,8 +57,9 @@ public class SrpConnectionPipelineTxIntegrationTests extends SrpConnectionTransa
assertNull(connection.exec());
List<Object> results = connection.closePipeline();
assertEquals(2, results.size());
assertEquals("bar", new String((byte[]) results.get(0)));
assertEquals("baz", new String((byte[]) results.get(1)));
// First element of each list is "OK"
assertEquals("bar", new String((byte[])((List<Object>)results.get(0)).get(1)));
assertEquals("baz", new String((byte[])((List<Object>)results.get(1)).get(1)));
}
@Test(expected=RedisPipelineException.class)
@@ -122,14 +97,25 @@ public class SrpConnectionPipelineTxIntegrationTests extends SrpConnectionTransa
connection.multi();
}
@SuppressWarnings("unchecked")
protected List<Object> getResults() {
assertNull(connection.exec());
List<Object> actual = connection.closePipeline();
List<Object> serializedResults = new ArrayList<Object>();
for (Object result : actual) {
Object convertedResult = convertResult(result);
serializedResults.add(convertedResult);
}
return serializedResults;
List<Object> pipelined = connection.closePipeline();
// We expect only the results of exec to be in the closed pipeline
assertEquals(1,pipelined.size());
List<Object> txResults = (List<Object>)pipelined.get(0);
// Return exec results and this test should behave exactly like its superclass
return convertResults(txResults);
}
@SuppressWarnings("unchecked")
protected List<Object> getResultsNoConversion() {
assertNull(connection.exec());
List<Object> pipelined = connection.closePipeline();
// We expect only the results of exec to be in the closed pipeline
assertEquals(1,pipelined.size());
List<Object> txResults = (List<Object>)pipelined.get(0);
// Return exec results and this test should behave exactly like its superclass
return txResults;
}
}

View File

@@ -16,22 +16,31 @@
package org.springframework.data.redis.connection.srp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.data.redis.RedisSystemException;
import org.junit.runner.RunWith;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.AbstractConnectionTransactionIntegrationTests;
import org.springframework.data.redis.connection.DefaultStringTuple;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.StringRedisConnection.StringTuple;
import org.springframework.data.redis.serializer.SerializationUtils;
import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import redis.reply.IntegerReply;
import redis.reply.Reply;
import redis.reply.StatusReply;
@@ -41,254 +50,359 @@ import org.springframework.test.annotation.IfProfileValue;
* @author Jennifer Hickey
*
*/
public class SrpConnectionTransactionIntegrationTests extends SrpConnectionIntegrationTests {
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("SrpConnectionIntegrationTests-context.xml")
public class SrpConnectionTransactionIntegrationTests extends AbstractConnectionTransactionIntegrationTests {
private boolean convert = true;
protected boolean convertResultToSet=false;
@Ignore
public void testMultiDiscard() {
}
protected boolean convertResultToStringTuples=false;
@Ignore
public void testMultiExec() {
}
protected boolean convertResultToTuples=false;
@Ignore
public void testUnwatch() {
}
protected boolean convertBooleanToLong=false;
@Ignore
public void testWatch() {
}
@Ignore
@Test
public void testExecWithoutMulti() {
}
@Ignore
@Test
public void testErrorInTx() {
}
/*
* Using blocking ops inside a tx does not make a lot of sense as it would
* require blocking the entire server in order to execute the block
* atomically, which in turn does not allow other clients to perform a push
* operation. *
*/
@Ignore
public void testBLPop() {
}
@Ignore
public void testBRPop() {
}
@Ignore
public void testBRPopLPush() {
}
@Ignore
public void testBLPopTimeout() {
}
@Ignore
public void testBRPopTimeout() {
}
@Ignore
public void testBRPopLPushTimeout() {
}
@Ignore("Pub/Sub not supported with transactions")
public void testPubSubWithNamedChannels() throws Exception {
}
@Ignore("Pub/Sub not supported with transactions")
public void testPubSubWithPatterns() throws Exception {
}
@Ignore
public void testNullKey() throws Exception {
}
@Ignore
public void testNullValue() throws Exception {
}
@Ignore
public void testHashNullKey() throws Exception {
}
@Ignore
public void testHashNullValue() throws Exception {
}
@Test(expected = RedisSystemException.class)
public void exceptionExecuteNative() throws Exception {
super.exceptionExecuteNative();
}
// SRP tx.exec() is now returning converted data types b/c it uses pipeline, but
// DefaultStringRedisConnection isn't converting byte[]s from exec() yet.
// We implement the conversion in convertResult(), but convertResult's a bit over-eager in certain scenarios, hence these overrides
protected boolean convertResultsToMap=false;
@Test
public void testExecute() {
convert = false;
super.testExecute();
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testScriptLoadEvalSha() {
convert = false;
super.testScriptLoadEvalSha();
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testEvalShaArrayStrings() {
convert = false;
super.testEvalShaArrayStrings();
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testEvalReturnString() {
convert = false;
super.testEvalReturnString();
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testEvalReturnArrayStrings() {
convert = false;
super.testEvalReturnArrayStrings();
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testDumpAndRestore() {
convert = false;
connection.set("testing", "12");
actual.add(connection.dump("testing".getBytes()));
List<Object> results = getResults();
initConnection();
public void testDel() {
connection.set("testing","123");
actual.add(connection.del("testing"));
actual.add((connection.get("testing")));
connection.restore("testing".getBytes(), 0, (byte[]) results.get(results.size() - 1));
actual.add(connection.get("testing"));
results = getResults();
assertEquals(3,results.size());
assertEquals(1l, results.get(0));
assertNull(results.get(1));
assertEquals("12", new String((byte[])results.get(2)));
actual.add(connection.exists("testing"));
verifyResults(Arrays.asList(new Object[] { true, false }));
}
@Test(expected = RedisSystemException.class)
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testRestoreExistingKey() {
connection.set("testing", "12");
actual.add(connection.dump("testing".getBytes()));
List<Object> results = getResults();
initConnection();
connection.restore("testing".getBytes(), 0, ((String)results.get(0)).getBytes());
getResults();
@Test
public void testSAdd() {
convertResultToSet = true;
super.testSAdd();
}
@Test
public void testSDiff() {
convertResultToSet = true;
super.testSDiff();
}
@Test
public void testSInter() {
convertResultToSet = true;
super.testSInter();
}
@Test
public void testSRem() {
convertResultToSet = true;
super.testSRem();
}
@Test
public void testSUnion() {
convertResultToSet = true;
super.testSUnion();
}
@Test
public void testSUnionStore() {
convertResultToSet = true;
super.testSUnionStore();
}
@Test
public void testSDiffStore() {
convertResultToSet = true;
super.testSDiffStore();
}
@Test
public void testSInterStore() {
convertResultToSet = true;
super.testSInterStore();
}
@Test
public void testZRemRangeByScore() {
convertResultToSet = true;
super.testZRemRangeByScore();
}
@Test
public void testZAddAndZRange() {
convertResultToSet = true;
super.testZAddAndZRange();
}
@Test
public void testZIncrBy() {
convertResultToSet = true;
actual.add(connection.zAdd("myset", 2, "Bob"));
actual.add(connection.zAdd("myset", 1, "James"));
actual.add(connection.zAdd("myset", 4, "Joe"));
actual.add(connection.zIncrBy("myset", 2, "Joe"));
actual.add(connection.zRangeByScore("myset", 6, 6));
verifyResults(
Arrays.asList(new Object[] { true, true, true, "6",
new LinkedHashSet<String>(Collections.singletonList("Joe")) }));
}
@Test
public void testZInterStore() {
convertResultToSet = true;
super.testZInterStore();
}
@Test(expected=UnsupportedOperationException.class)
public void testZInterStoreAggWeights() {
super.testZInterStoreAggWeights();
}
@Test(expected=UnsupportedOperationException.class)
public void testZUnionStoreAggWeights() {
super.testZUnionStoreAggWeights();
}
@Test
public void testHIncrByDouble() {
actual.add(connection.hSet("test", "key", "2.9"));
actual.add(connection.hIncrBy("test", "key", 3.5));
actual.add(connection.hGet("test", "key"));
// SRP returns byte[] from hIncrBy
verifyResults(Arrays.asList(new Object[] { true, "6.4", "6.4" }));
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testRestoreTtl() {
convert = false;
super.testRestoreTtl();
public void testIncrByDouble() {
connection.set("tdb", "4.5");
actual.add(connection.incrBy("tdb", 7.2));
actual.add(connection.get("tdb"));
// SRP returns byte[] fro incrBy
verifyResults(Arrays.asList(new Object[] { "11.7", "11.7" }));
}
@Test
public void testZRevRangeByScoreOffsetCount() {
convert = false;
super.testZRevRangeByScoreOffsetCount();
public void testBitSet() throws Exception {
convertLongToBoolean = false;
String key = "bitset-test";
connection.setBit(key, 0, false);
connection.setBit(key, 1, true);
actual.add(connection.getBit(key, 0));
actual.add(connection.getBit(key, 1));
// Lettuce setBit returns Long instead of void
verifyResults(Arrays.asList(new Object[] { 0l, 0l, 0l, 1l }));
}
@Test
public void testZRevRangeByScore() {
convert = false;
super.testZRevRangeByScore();
public void testBitCount() {
convertBooleanToLong = true;
super.testBitCount();
}
@Test
public void testZRevRangeByScoreWithScoresOffsetCount() {
convert = false;
super.testZRevRangeByScoreWithScoresOffsetCount();
public void testSortStoreNullParams() {
convertBooleanToLong = true;
super.testSortNullParams();
}
@Test
public void testZRangeWithScores() {
convertResultToStringTuples = true;
convertResultToSet = true;
super.testZRangeWithScores();
}
@Test
public void testZRangeByScore() {
convertResultToSet = true;
super.testZRangeByScore();
}
@Test
public void testZRangeByScoreOffsetCount() {
convertResultToSet = true;
super.testZRangeByScoreOffsetCount();
}
@Test
public void testZRangeByScoreWithScores() {
convertResultToStringTuples = true;
convertResultToSet = true;
super.testZRangeByScoreWithScores();
}
@Test
public void testZRangeByScoreWithScoresOffsetCount() {
convertResultToStringTuples = true;
convertResultToSet = true;
super.testZRangeByScoreWithScoresOffsetCount();
}
@Test
public void testZRevRange() {
convertResultToSet = true;
super.testZRevRange();
}
@Test
public void testZRevRangeWithScores() {
convertResultToStringTuples = true;
convertResultToSet = true;
super.testZRevRangeWithScores();
}
@Test
public void testZRevRangeByScoreWithScores() {
convert = false;
convertResultToTuples = true;
super.testZRevRangeByScoreWithScores();
}
@Test(expected = UnsupportedOperationException.class)
@Test
public void testZRem() {
convertResultToSet = true;
super.testZRem();
}
@Test
public void testZRemRangeByRank() {
convertResultToSet = true;
super.testZRemRangeByRank();
}
@Test
public void testZScore() {
actual.add(connection.zAdd("myset", 2, "Bob"));
actual.add(connection.zAdd("myset", 1, "James"));
actual.add(connection.zAdd("myset", 3, "Joe"));
actual.add(connection.zScore("myset", "Joe"));
verifyResults(Arrays.asList(new Object[] { true, true, true, "3" }));
}
@Test
public void testZUnionStore() {
convertResultToSet = true;
super.testZUnionStore();
}
@Test
public void testHSetGet() throws Exception {
convertResultsToMap = true;
super.testHSetGet();
}
@Test
public void testHKeys() {
convertResultToSet = true;
super.testHKeys();
}
@SuppressWarnings("unchecked")
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testScriptKill() {
// Impossible to call script kill in a tx because you can't issue the
// exec command while Redis is running a script
connection.scriptKill();
public void testEvalShaArrayStrings() {
getResults();
String sha1 = connection.scriptLoad("return {KEYS[1],ARGV[1]}");
initConnection();
actual.add(connection.evalSha(sha1, ReturnType.MULTI, 1, "key1", "arg1"));
List<Object> results = getResults();
List<String> scriptResults = (List<String>) results.get(0);
assertEquals(Arrays.asList(new Object[] { "key1", "arg1" }),
scriptResults);
}
protected void initConnection() {
connection.multi();
@SuppressWarnings("unchecked")
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testEvalReturnArrayStrings() {
actual.add(connection.eval("return {KEYS[1],ARGV[1]}", ReturnType.MULTI, 1, "foo", "bar"));
List<String> result = (List<String>) getResults().get(0);
assertEquals(Arrays.asList(new Object[] { "foo", "bar" }), result);
}
protected List<Object> getResults() {
List<Object> actual = connection.exec();
List<Object> serializedResults = new ArrayList<Object>();
for (Object result : actual) {
Object convertedResult = convertResult(result);
serializedResults.add(convertedResult);
}
return serializedResults;
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testEvalReturnFalse() {
actual.add(connection.eval("return false", ReturnType.BOOLEAN, 0));
verifyResults(Arrays.asList(new Object[] { null }));
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testEvalReturnArrayNumbers() {
convertLongToBoolean = false;
super.testEvalReturnArrayNumbers();
}
@Test
@IfProfileValue(name = "redisVersion", value = "2.6")
public void testEvalReturnArrayTrues() {
convertLongToBoolean = false;
super.testEvalReturnArrayTrues();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
protected Object convertResult(Object result) {
if(! convert) {
return result;
}
if (result instanceof List && !(((List) result).isEmpty())
&& ((List) result).get(0) instanceof byte[]) {
return (SerializationUtils.deserialize((List<byte[]>) result, stringSerializer));
} else if (result instanceof byte[]) {
return (stringSerializer.deserialize((byte[]) result));
} else if (result instanceof Map
&& ((Map) result).keySet().iterator().next() instanceof byte[]) {
return (SerializationUtils.deserialize((Map) result, stringSerializer));
} else if (result instanceof Set && !(((Set) result).isEmpty())) {
Object firstResult = ((Set) result).iterator().next();
if(firstResult instanceof byte[]) {
return (SerializationUtils.deserialize((Set) result, stringSerializer));
}
Object convertedResult = super.convertResult(result);
if (convertedResult instanceof Set && !(((Set) convertedResult).isEmpty())) {
Object firstResult = ((Set) convertedResult).iterator().next();
if(firstResult instanceof Tuple) {
Set<StringTuple> tuples = new LinkedHashSet<StringTuple>();
for (Tuple value : ((Set<Tuple>) result)) {
for (Tuple value : ((Set<Tuple>) convertedResult)) {
DefaultStringTuple tuple = new DefaultStringTuple(value,stringSerializer.deserialize(value.getValue()));
tuples.add(tuple);
}
return tuples;
}
}else if (convertedResult instanceof Reply[]) {
if (convertResultToStringTuples) {
Set<Tuple> tuples = SrpConverters.toTupleSet((Reply[])convertedResult);
Collection<StringTuple> stringTuples;
if(convertResultToSet) {
stringTuples = new LinkedHashSet<StringTuple>();
}else {
stringTuples = new ArrayList<StringTuple>();
}
for (Tuple tuple : tuples) {
stringTuples.add(new DefaultStringTuple(tuple, new String(tuple.getValue())));
}
return stringTuples;
} else if(convertResultToTuples) {
return SrpConverters.toTupleSet((Reply[])convertedResult);
}
else if (convertResultToSet) {
return SerializationUtils.deserialize(SrpConverters.toBytesSet((Reply[])convertedResult),
stringSerializer);
} else if(((Reply[]) convertedResult).length > 0 && ((Reply[])convertedResult)[0] instanceof IntegerReply) {
if(convertLongToBoolean) {
return SrpConverters.toBooleanList((Reply[])convertedResult);
}
List<Long> results = new ArrayList<Long>();
for(Reply reply: (Reply[])convertedResult) {
results.add(((IntegerReply)reply).data());
}
return results;
} else if(((Reply[]) convertedResult).length > 0 && ((Reply[])convertedResult)[0] instanceof StatusReply) {
List<String> statuses = new ArrayList<String>();
for(Reply reply: (Reply[])convertedResult) {
statuses.add(((StatusReply)reply).data());
}
return statuses;
} else if(convertResultsToMap) {
return (SerializationUtils.deserialize(SrpConverters.toBytesMap((Reply[])convertedResult), stringSerializer));
} else {
return SerializationUtils.deserialize(
SrpConverters.toBytesList((Reply[]) convertedResult), stringSerializer);
}
} else if(convertBooleanToLong && result instanceof Boolean) {
if((Boolean)result) {
return 1l;
}
return 0l;
}
return result;
return convertedResult;
}
protected void verifyResults(List<Object> expected) {
List<Object> expectedTx = new ArrayList<Object>();
for (int i = 0; i < actual.size(); i++) {
expectedTx.add(null);
}
assertEquals(expectedTx, actual);
List<Object> results = getResults();
assertEquals(expected, results);
@Override
protected DataAccessException convertException(Exception ex) {
return SrpConverters.toDataAccessException(ex);
}
}