Commit c002a00e authored by Marc Gravell's avatar Marc Gravell

migrate BookSleeve "Scripting" suite; fix "eval inside transaction" bug

parent 6c191d56
...@@ -188,11 +188,11 @@ where Attribute.IsDefined(method, typeof(TestAttribute)) ...@@ -188,11 +188,11 @@ where Attribute.IsDefined(method, typeof(TestAttribute))
} }
Console.WriteLine("Passed: {0}; Failed: {1}", pass, fail); Console.WriteLine("Passed: {0}; Failed: {1}", pass, fail);
foreach (var msg in epicFail) Console.WriteLine(msg); foreach (var msg in epicFail) Console.WriteLine(msg);
#if DEBUG //#if DEBUG
Console.WriteLine(); // Console.WriteLine();
Console.WriteLine("Callbacks: {0:###,###,##0} sync, {1:###,###,##0} async", // Console.WriteLine("Callbacks: {0:###,###,##0} sync, {1:###,###,##0} async",
BookSleeve.RedisConnectionBase.AllSyncCallbacks, BookSleeve.RedisConnectionBase.AllAsyncCallbacks); // BookSleeve.RedisConnectionBase.AllSyncCallbacks, BookSleeve.RedisConnectionBase.AllAsyncCallbacks);
#endif //#endif
} }
} }
......
//using BookSleeve; using NUnit.Framework;
//using NUnit.Framework; using System;
//using System; using System.Collections.Generic;
//using System.Collections.Generic; using System.Linq;
//using System.Linq; using System.Text;
//using System.Text; using System.Threading.Tasks;
//using System.Threading.Tasks; using StackExchange.Redis;
using System.Diagnostics;
//namespace Tests
//{ namespace Tests
// [TestFixture] {
// public class Scripting [TestFixture]
// { public class Scripting
// static RedisConnection GetScriptConn(bool allowAdmin = false) {
// { static ConnectionMultiplexer GetScriptConn(bool allowAdmin = false)
// var conn = Config.GetUnsecuredConnection(waitForOpen: true, allowAdmin: allowAdmin); {
// if (!conn.Features.Scripting) int syncTimeout = 5000;
// { if (Debugger.IsAttached) syncTimeout = 500000;
// Assert.Inconclusive("The server does not support scripting"); var muxer = Config.GetUnsecuredConnection(waitForOpen: true, allowAdmin: allowAdmin, syncTimeout: syncTimeout);
// } if (!Config.GetFeatures(muxer).Scripting)
// return conn; {
Assert.Inconclusive("The server does not support scripting");
// } }
// [Test] return muxer;
// public void ClientScripting()
// { }
// using (var conn = GetScriptConn()) [Test]
// { public void ClientScripting()
// var result = conn.Wait(conn.Scripting.Eval(0, "return redis.call('info','server')", null, null)); {
// } using (var conn = GetScriptConn())
// } {
var result = conn.GetDatabase().ScriptEvaluate("return redis.call('info','server')", null, null);
// [Test] }
// public void BasicScripting() }
// {
// using (var conn = GetScriptConn()) [Test]
// { public void BasicScripting()
// var noCache = conn.Scripting.Eval(0, "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", {
// new[] { "key1", "key2" }, new[] { "first", "second" }, useCache: false); using (var muxer = GetScriptConn())
// var cache = conn.Scripting.Eval(0, "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", {
// new[] { "key1", "key2" }, new[] { "first", "second" }, useCache: true); var conn = muxer.GetDatabase();
// var results = (object[])conn.Wait(noCache); var noCache = conn.ScriptEvaluateAsync("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}",
// Assert.AreEqual(4, results.Length); new RedisKey[] { "key1", "key2" }, new RedisValue[] { "first", "second" });
// Assert.AreEqual("key1", results[0]); var cache = conn.ScriptEvaluateAsync("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}",
// Assert.AreEqual("key2", results[1]); new RedisKey[] { "key1", "key2" }, new RedisValue[] { "first", "second" });
// Assert.AreEqual("first", results[2]); var results = (string[])conn.Wait(noCache);
// Assert.AreEqual("second", results[3]); Assert.AreEqual(4, results.Length);
Assert.AreEqual("key1", results[0]);
// results = (object[])conn.Wait(cache); Assert.AreEqual("key2", results[1]);
// Assert.AreEqual(4, results.Length); Assert.AreEqual("first", results[2]);
// Assert.AreEqual("key1", results[0]); Assert.AreEqual("second", results[3]);
// Assert.AreEqual("key2", results[1]);
// Assert.AreEqual("first", results[2]); results = (string[])conn.Wait(cache);
// Assert.AreEqual("second", results[3]); Assert.AreEqual(4, results.Length);
// } Assert.AreEqual("key1", results[0]);
// } Assert.AreEqual("key2", results[1]);
// [Test] Assert.AreEqual("first", results[2]);
// public void KeysScripting() Assert.AreEqual("second", results[3]);
// { }
// using (var conn = GetScriptConn()) }
// { [Test]
// conn.Strings.Set(0, "foo", "bar"); public void KeysScripting()
// var result = (string)conn.Wait(conn.Scripting.Eval(0, "return redis.call('get', KEYS[1])", new[] { "foo" }, null)); {
// Assert.AreEqual("bar", result); using (var muxer = GetScriptConn())
// } {
// } var conn = muxer.GetDatabase();
conn.StringSet("foo", "bar");
// [Test] var result = (string)conn.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { "foo" }, null);
// public void TestRandomThingFromForum() Assert.AreEqual("bar", result);
// { }
// const string script = @"local currentVal = tonumber(redis.call('GET', KEYS[1])); }
// if (currentVal <= 0 ) then return 1 elseif (currentVal - (tonumber(ARGV[1])) < 0 ) then return 0 end;
// return redis.call('INCRBY', KEYS[1], -tonumber(ARGV[1]));"; [Test]
public void TestRandomThingFromForum()
// using (var conn = GetScriptConn()) {
// { const string script = @"local currentVal = tonumber(redis.call('GET', KEYS[1]));
// conn.Strings.Set(0, "A", "0"); if (currentVal <= 0 ) then return 1 elseif (currentVal - (tonumber(ARGV[1])) < 0 ) then return 0 end;
// conn.Strings.Set(0, "B", "5"); return redis.call('INCRBY', KEYS[1], -tonumber(ARGV[1]));";
// conn.Strings.Set(0, "C", "10");
using (var muxer = GetScriptConn())
// var a = conn.Scripting.Eval(0, script, new[] { "A" }, new object[] { 6 }); {
// var b = conn.Scripting.Eval(0, script, new[] { "B" }, new object[] { 6 }); var conn = muxer.GetDatabase();
// var c = conn.Scripting.Eval(0, script, new[] { "C" }, new object[] { 6 }); conn.StringSetAsync("A", "0");
conn.StringSetAsync("B", "5");
// var vals = conn.Strings.GetString(0, new[] { "A", "B", "C" }); conn.StringSetAsync("C", "10");
// Assert.AreEqual(1, conn.Wait(a)); // exit code when current val is non-positive var a = conn.ScriptEvaluateAsync(script, new RedisKey[] { "A" }, new RedisValue[] { 6 });
// Assert.AreEqual(0, conn.Wait(b)); // exit code when result would be negative var b = conn.ScriptEvaluateAsync(script, new RedisKey[] { "B" }, new RedisValue[] { 6 });
// Assert.AreEqual(4, conn.Wait(c)); // 10 - 6 = 4 var c = conn.ScriptEvaluateAsync(script, new RedisKey[] { "C" }, new RedisValue[] { 6 });
// Assert.AreEqual("0", conn.Wait(vals)[0]);
// Assert.AreEqual("5", conn.Wait(vals)[1]); var vals = conn.StringGetAsync(new RedisKey[] { "A", "B", "C" });
// Assert.AreEqual("4", conn.Wait(vals)[2]);
// } Assert.AreEqual(1, (long)conn.Wait(a)); // exit code when current val is non-positive
// } Assert.AreEqual(0, (long)conn.Wait(b)); // exit code when result would be negative
Assert.AreEqual(4, (long)conn.Wait(c)); // 10 - 6 = 4
// [Test] Assert.AreEqual("0", (string)conn.Wait(vals)[0]);
// public void HackyGetPerf() Assert.AreEqual("5", (string)conn.Wait(vals)[1]);
// { Assert.AreEqual("4", (string)conn.Wait(vals)[2]);
// using (var conn = GetScriptConn()) }
// { }
// conn.Strings.Set(0, "foo", "bar");
// var key = Config.CreateUniqueName(); [Test]
// var result = (long)conn.Wait(conn.Scripting.Eval(0, @" public void HackyGetPerf()
//redis.call('psetex', KEYS[1], 60000, 'timing') {
//for i = 1,100000 do using (var muxer = GetScriptConn())
// redis.call('set', 'ignore','abc') {
//end var conn = muxer.GetDatabase();
//local timeTaken = 60000 - redis.call('pttl', KEYS[1]) conn.StringSetAsync("foo", "bar");
//redis.call('del', KEYS[1]) var key = Config.CreateUniqueName();
//return timeTaken var result = (long)conn.ScriptEvaluate(@"
//", new[] { key }, null)); redis.call('psetex', KEYS[1], 60000, 'timing')
// Console.WriteLine(result); for i = 1,100000 do
// Assert.IsTrue(result > 0); redis.call('set', 'ignore','abc')
// } end
// } local timeTaken = 60000 - redis.call('pttl', KEYS[1])
redis.call('del', KEYS[1])
// [Test] return timeTaken
// public void MultiIncrWithoutReplies() ", new RedisKey[] { key }, null);
// { Console.WriteLine(result);
// using (var conn = GetScriptConn()) Assert.IsTrue(result > 0);
// { }
}
// const int DB = 0; // any database number
// // prime some initial values [Test]
// conn.Keys.Remove(DB, new[] { "a", "b", "c" }); public void MultiIncrWithoutReplies()
// conn.Strings.Increment(DB, "b"); {
// conn.Strings.Increment(DB, "c"); using (var muxer = GetScriptConn())
// conn.Strings.Increment(DB, "c"); {
const int DB = 0; // any database number
// // run the script, passing "a", "b", "c", "c" to var conn = muxer.GetDatabase(DB);
// // increment a & b by 1, c twice // prime some initial values
// var result = conn.Scripting.Eval(DB, conn.KeyDeleteAsync(new RedisKey[] { "a", "b", "c" });
// @"for i,key in ipairs(KEYS) do redis.call('incr', key) end", conn.StringIncrementAsync("b");
// new[] { "a", "b", "c", "c" }, // <== aka "KEYS" in the script conn.StringIncrementAsync("c");
// null); // <== aka "ARGV" in the script conn.StringIncrementAsync("c");
// // check the incremented values // run the script, passing "a", "b", "c", "c" to
// var a = conn.Strings.GetInt64(DB, "a"); // increment a & b by 1, c twice
// var b = conn.Strings.GetInt64(DB, "b"); var result = conn.ScriptEvaluateAsync(
// var c = conn.Strings.GetInt64(DB, "c"); @"for i,key in ipairs(KEYS) do redis.call('incr', key) end",
new RedisKey[] { "a", "b", "c", "c" }, // <== aka "KEYS" in the script
// Assert.IsNull(conn.Wait(result), "result"); null); // <== aka "ARGV" in the script
// Assert.AreEqual(1, conn.Wait(a), "a");
// Assert.AreEqual(2, conn.Wait(b), "b"); // check the incremented values
// Assert.AreEqual(4, conn.Wait(c), "c"); var a = conn.StringGetAsync("a");
// } var b = conn.StringGetAsync("b");
// } var c = conn.StringGetAsync("c");
// [Test] Assert.IsTrue(conn.Wait(result).IsNull, "result");
// public void MultiIncrByWithoutReplies() Assert.AreEqual(1, (long)conn.Wait(a), "a");
// { Assert.AreEqual(2, (long)conn.Wait(b), "b");
// using (var conn = GetScriptConn()) Assert.AreEqual(4, (long)conn.Wait(c), "c");
// { }
// const int DB = 0; // any database number }
// // prime some initial values
// conn.Keys.Remove(DB, new[] { "a", "b", "c" }); [Test]
// conn.Strings.Increment(DB, "b"); public void MultiIncrByWithoutReplies()
// conn.Strings.Increment(DB, "c"); {
// conn.Strings.Increment(DB, "c"); using (var muxer = GetScriptConn())
{
// // run the script, passing "a", "b", "c" and 1,2,3 const int DB = 0; // any database number
// // increment a & b by 1, c twice var conn = muxer.GetDatabase(DB);
// var result = conn.Scripting.Eval(DB, // prime some initial values
// @"for i,key in ipairs(KEYS) do redis.call('incrby', key, ARGV[i]) end", conn.KeyDeleteAsync(new RedisKey[] { "a", "b", "c" });
// new[] { "a", "b", "c" }, // <== aka "KEYS" in the script conn.StringIncrementAsync("b");
// new object[] {1,1,2}); // <== aka "ARGV" in the script conn.StringIncrementAsync("c");
conn.StringIncrementAsync("c");
// // check the incremented values
// var a = conn.Strings.GetInt64(DB, "a"); //run the script, passing "a", "b", "c" and 1,2,3
// var b = conn.Strings.GetInt64(DB, "b"); // increment a &b by 1, c twice
// var c = conn.Strings.GetInt64(DB, "c"); var result = conn.ScriptEvaluateAsync(
@"for i,key in ipairs(KEYS) do redis.call('incrby', key, ARGV[i]) end",
// Assert.IsNull(conn.Wait(result), "result"); new RedisKey[] { "a", "b", "c" }, // <== aka "KEYS" in the script
// Assert.AreEqual(1, conn.Wait(a), "a"); new RedisValue[] { 1, 1, 2 }); // <== aka "ARGV" in the script
// Assert.AreEqual(2, conn.Wait(b), "b");
// Assert.AreEqual(4, conn.Wait(c), "c"); // check the incremented values
// } var a = conn.StringGetAsync("a");
// } var b = conn.StringGetAsync("b");
var c = conn.StringGetAsync("c");
// [Test]
// public void DisableStringInference() Assert.IsTrue(conn.Wait(result).IsNull, "result");
// { Assert.AreEqual(1, (long)conn.Wait(a), "a");
// using (var conn = GetScriptConn()) Assert.AreEqual(2, (long)conn.Wait(b), "b");
// { Assert.AreEqual(4, (long)conn.Wait(c), "c");
// conn.Strings.Set(0, "foo", "bar"); }
// var result = (byte[])conn.Wait(conn.Scripting.Eval(0, "return redis.call('get', KEYS[1])", new[] { "foo" }, null, inferStrings: false)); }
// Assert.AreEqual("bar", Encoding.UTF8.GetString(result));
// } [Test]
// } public void DisableStringInference()
{
// [Test] using (var muxer = GetScriptConn())
// public void FlushDetection() {
// { // we don't expect this to handle everything; we just expect it to be predictable var conn = muxer.GetDatabase(0);
// using (var conn = GetScriptConn(allowAdmin: true)) conn.StringSet("foo", "bar");
// { var result = (byte[])conn.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { "foo" });
// conn.Strings.Set(0, "foo", "bar"); Assert.AreEqual("bar", Encoding.UTF8.GetString(result));
// var result = conn.Wait(conn.Scripting.Eval(0, "return redis.call('get', KEYS[1])", new[] { "foo" }, null)); }
// Assert.AreEqual("bar", result); }
// // now cause all kinds of problems [Test]
// conn.Server.FlushScriptCache(); public void FlushDetection()
{ // we don't expect this to handle everything; we just expect it to be predictable
// // expect this one to fail using (var muxer = GetScriptConn(allowAdmin: true))
// try { {
// conn.Wait(conn.Scripting.Eval(0, "return redis.call('get', KEYS[1])", new[] { "foo" }, null)); var conn = muxer.GetDatabase(0);
// Assert.Fail("Shouldn't have got here"); conn.StringSet("foo", "bar");
// } var result = (string)conn.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { "foo" }, null);
// catch (RedisException) { } Assert.AreEqual("bar", result);
// catch { Assert.Fail("Expected RedisException"); }
// now cause all kinds of problems
// result = conn.Wait(conn.Scripting.Eval(0, "return redis.call('get', KEYS[1])", new[] { "foo" }, null)); Config.GetServer(muxer).ScriptFlush();
// Assert.AreEqual("bar", result);
// } //expect this one to fail
// } try
{
// [Test] conn.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { "foo" }, null);
// public void PrepareScript() Assert.Fail("Shouldn't have got here");
// { }
// string[] scripts = { "return redis.call('get', KEYS[1])", "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" }; catch (RedisException)
// using (var conn = GetScriptConn(allowAdmin: true)) { }
// { catch
// conn.Server.FlushScriptCache(); { Assert.Fail("Expected RedisException"); }
// // when vanilla result = (string)conn.ScriptEvaluate("return redis.call('get', KEYS[1])", new RedisKey[] { "foo" }, null);
// conn.Wait(conn.Scripting.Prepare(scripts)); Assert.AreEqual("bar", result);
}
// // when known to exist }
// conn.Wait(conn.Scripting.Prepare(scripts));
// } [Test]
// using (var conn = GetScriptConn()) public void PrepareScript()
// { {
// // when vanilla string[] scripts = { "return redis.call('get', KEYS[1])", "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" };
// conn.Wait(conn.Scripting.Prepare(scripts)); using (var muxer = GetScriptConn(allowAdmin: true))
{
// // when known to exist var server = Config.GetServer(muxer);
// conn.Wait(conn.Scripting.Prepare(scripts)); server.ScriptFlush();
// // when known to exist // when vanilla
// conn.Wait(conn.Scripting.Prepare(scripts)); server.ScriptLoad(scripts[0]);
// } server.ScriptLoad(scripts[1]);
// }
// [Test] //when known to exist
// public void NonAsciiScripts() server.ScriptLoad(scripts[0]);
// { server.ScriptLoad(scripts[1]);
// using (var conn = GetScriptConn()) }
// { using (var muxer = GetScriptConn())
// const string evil = "return '僕'"; {
var server = Config.GetServer(muxer);
// var task = conn.Scripting.Prepare(evil);
// conn.Wait(task); //when vanilla
// var result = conn.Wait(conn.Scripting.Eval(0, evil, null, null)); server.ScriptLoad(scripts[0]);
// Assert.AreEqual("僕", result); server.ScriptLoad(scripts[1]);
// }
// } //when known to exist
server.ScriptLoad(scripts[0]);
// [Test, ExpectedException(typeof(RedisException), ExpectedMessage="oops")] server.ScriptLoad(scripts[1]);
// public void ScriptThrowsError()
// { //when known to exist
// using (var conn = GetScriptConn()) server.ScriptLoad(scripts[0]);
// { server.ScriptLoad(scripts[1]);
// var result = conn.Scripting.Eval(0, "return redis.error_reply('oops')", null, null); }
// conn.Wait(result); }
// } [Test]
// } public void NonAsciiScripts()
{
// [Test] using (var muxer = GetScriptConn())
// public void ScriptThrowsErrorInsideTransaction() {
// { const string evil = "return '僕'";
// using (var conn = GetScriptConn()) var conn = muxer.GetDatabase(0);
// { Config.GetServer(muxer).ScriptLoad(evil);
// const int db = 0;
// const string key = "ScriptThrowsErrorInsideTransaction"; var result = (string)conn.ScriptEvaluate(evil, null, null);
// conn.Keys.Remove(db, key); Assert.AreEqual("僕", result);
// var beforeTran = conn.Strings.GetInt64(db, key); }
// Assert.IsNull(conn.Wait(beforeTran)); }
// using (var tran = conn.CreateTransaction())
// { [Test, ExpectedException(typeof(RedisServerException), ExpectedMessage = "oops")]
// var a = tran.Strings.Increment(db, key); public void ScriptThrowsError()
// var b = tran.Scripting.Eval(db, "return redis.error_reply('oops')", null, null); {
// var c = tran.Strings.Increment(db, key); using (var muxer = GetScriptConn())
// var complete = tran.Execute(); {
var conn = muxer.GetDatabase(0);
// Assert.IsTrue(tran.Wait(complete)); var result = conn.ScriptEvaluateAsync("return redis.error_reply('oops')", null, null);
// Assert.IsTrue(a.IsCompleted); try
// Assert.IsTrue(c.IsCompleted); {
// Assert.AreEqual(1L, a.Result); conn.Wait(result);
// Assert.AreEqual(2L, c.Result); } catch(AggregateException ex)
{
// Assert.IsTrue(b.IsFaulted); throw ex.InnerExceptions[0];
// Assert.AreEqual(1, b.Exception.InnerExceptions.Count); }
// var ex = b.Exception.InnerExceptions.Single(); }
// Assert.IsInstanceOf<RedisException>(ex); }
// Assert.AreEqual("oops", ex.Message);
[Test]
// } public void ScriptThrowsErrorInsideTransaction()
// var afterTran = conn.Strings.GetInt64(db, key); {
// Assert.AreEqual(2L, conn.Wait(afterTran)); using (var muxer = GetScriptConn())
// } {
// } const int db = 0;
const string key = "ScriptThrowsErrorInsideTransaction";
var conn = muxer.GetDatabase(db);
conn.KeyDeleteAsync(key);
// [Test] var beforeTran = (string)conn.StringGet(key);
// public void ChangeDbInScript() Assert.IsNull(beforeTran);
// { var tran = conn.CreateTransaction();
// using (var conn = GetScriptConn()) {
// { var a = tran.StringIncrementAsync(key);
// conn.Strings.Set(1, "foo", "db 1"); var b = tran.ScriptEvaluateAsync("return redis.error_reply('oops')", null, null);
// conn.Strings.Set(2, "foo", "db 2"); var c = tran.StringIncrementAsync(key);
var complete = tran.ExecuteAsync();
// var evalResult = conn.Scripting.Eval(2, @"redis.call('select', 1)
//return redis.call('get','foo')", null, null); Assert.IsTrue(tran.Wait(complete));
// var getResult = conn.Strings.GetString(2, "foo"); Assert.IsTrue(a.IsCompleted);
Assert.IsTrue(c.IsCompleted);
// Assert.AreEqual("db 1", conn.Wait(evalResult)); Assert.AreEqual(1L, a.Result);
// // now, our connection thought it was in db 2, but the script changed to db 1 Assert.AreEqual(2L, c.Result);
// Assert.AreEqual("db 2", conn.Wait(getResult));
Assert.IsTrue(b.IsFaulted);
// } Assert.AreEqual(1, b.Exception.InnerExceptions.Count);
// } var ex = b.Exception.InnerExceptions.Single();
// } Assert.IsInstanceOf<RedisException>(ex);
//} Assert.AreEqual("oops", ex.Message);
}
var afterTran = conn.StringGetAsync(key);
Assert.AreEqual(2L, (long)conn.Wait(afterTran));
}
}
[Test]
public void ChangeDbInScript()
{
using (var muxer = GetScriptConn())
{
muxer.GetDatabase(1).StringSet("foo", "db 1");
muxer.GetDatabase(2).StringSet("foo", "db 2");
var conn = muxer.GetDatabase(2);
var evalResult = conn.ScriptEvaluateAsync(@"redis.call('select', 1)
return redis.call('get','foo')", null, null);
var getResult = conn.StringGetAsync("foo");
Assert.AreEqual("db 1", (string)conn.Wait(evalResult));
// now, our connection thought it was in db 2, but the script changed to db 1
Assert.AreEqual("db 2", (string)conn.Wait(getResult));
}
}
[Test]
public void ChangeDbInTranScript()
{
using (var muxer = GetScriptConn())
{
muxer.GetDatabase(1).StringSet("foo", "db 1");
muxer.GetDatabase(2).StringSet("foo", "db 2");
var conn = muxer.GetDatabase(2);
var tran = conn.CreateTransaction();
var evalResult = tran.ScriptEvaluateAsync(@"redis.call('select', 1)
return redis.call('get','foo')", null, null);
var getResult = tran.StringGetAsync("foo");
Assert.IsTrue(tran.Execute());
Assert.AreEqual("db 1", (string)conn.Wait(evalResult));
// now, our connection thought it was in db 2, but the script changed to db 1
Assert.AreEqual("db 2", (string)conn.Wait(getResult));
}
}
}
}
...@@ -273,9 +273,18 @@ internal Message GetSelectDatabaseCommand(int targetDatabase, Message message) ...@@ -273,9 +273,18 @@ internal Message GetSelectDatabaseCommand(int targetDatabase, Message message)
} }
return null; return null;
} }
if(message.Command == RedisCommand.SELECT)
{
// this could come from an EVAL/EVALSHA inside a transaction, for example; we'll accept it
bridge.Trace("Switching database: " + targetDatabase);
currentDatabase = targetDatabase;
return null;
}
if (TransactionActive) if (TransactionActive)
{// should never see this, since the API doesn't allow it; thus not too worried about ExceptionFactory {// should never see this, since the API doesn't allow it; thus not too worried about ExceptionFactory
throw new RedisCommandException("Multiple databases inside a transaction are not currently supported" + targetDatabase); throw new RedisCommandException("Multiple databases inside a transaction are not currently supported: " + targetDatabase);
} }
if (available != 0 && targetDatabase >= available) // we positively know it is out of range if (available != 0 && targetDatabase >= available) // we positively know it is out of range
...@@ -284,12 +293,16 @@ internal Message GetSelectDatabaseCommand(int targetDatabase, Message message) ...@@ -284,12 +293,16 @@ internal Message GetSelectDatabaseCommand(int targetDatabase, Message message)
} }
bridge.Trace("Switching database: " + targetDatabase); bridge.Trace("Switching database: " + targetDatabase);
currentDatabase = targetDatabase; currentDatabase = targetDatabase;
return targetDatabase < DefaultRedisDatabaseCount return GetSelectDatabaseCommand(targetDatabase);
? ReusableChangeDatabaseCommands[targetDatabase] // 0-15 by default
: Message.Create(targetDatabase, CommandFlags.FireAndForget, RedisCommand.SELECT);
} }
return null; return null;
} }
internal static Message GetSelectDatabaseCommand(int targetDatabase)
{
return targetDatabase < DefaultRedisDatabaseCount
? ReusableChangeDatabaseCommands[targetDatabase] // 0-15 by default
: Message.Create(targetDatabase, CommandFlags.FireAndForget, RedisCommand.SELECT);
}
internal int GetSentAwaitingResponseCount() internal int GetSentAwaitingResponseCount()
{ {
......
...@@ -7,6 +7,7 @@ namespace StackExchange.Redis ...@@ -7,6 +7,7 @@ namespace StackExchange.Redis
/// </summary> /// </summary>
public abstract class RedisResult public abstract class RedisResult
{ {
// internally, this is very similar to RawResult, except it is designed to be usable // internally, this is very similar to RawResult, except it is designed to be usable
// outside of the IO-processing pipeline: the buffers are standalone, etc // outside of the IO-processing pipeline: the buffers are standalone, etc
...@@ -42,6 +43,11 @@ internal static RedisResult TryCreate(PhysicalConnection connection, RawResult r ...@@ -42,6 +43,11 @@ internal static RedisResult TryCreate(PhysicalConnection connection, RawResult r
} }
} }
/// <summary>
/// Indicates whether this result was a null result
/// </summary>
public abstract bool IsNull { get; }
/// <summary> /// <summary>
/// Interprets the result as a String /// Interprets the result as a String
/// </summary> /// </summary>
...@@ -168,6 +174,10 @@ internal static RedisResult TryCreate(PhysicalConnection connection, RawResult r ...@@ -168,6 +174,10 @@ internal static RedisResult TryCreate(PhysicalConnection connection, RawResult r
internal abstract string[] AsStringArray(); internal abstract string[] AsStringArray();
private sealed class ArrayRedisResult : RedisResult private sealed class ArrayRedisResult : RedisResult
{ {
public override bool IsNull
{
get { return value == null; }
}
private readonly RedisResult[] value; private readonly RedisResult[] value;
public ArrayRedisResult(RedisResult[] value) public ArrayRedisResult(RedisResult[] value)
{ {
...@@ -275,6 +285,10 @@ public ErrorRedisResult(string value) ...@@ -275,6 +285,10 @@ public ErrorRedisResult(string value)
if (value == null) throw new ArgumentNullException("value"); if (value == null) throw new ArgumentNullException("value");
this.value = value; this.value = value;
} }
public override bool IsNull
{
get { return value == null; }
}
public override string ToString() { return value; } public override string ToString() { return value; }
internal override bool AsBoolean() { throw new RedisServerException(value); } internal override bool AsBoolean() { throw new RedisServerException(value); }
...@@ -326,6 +340,11 @@ public SingleRedisResult(RedisValue value) ...@@ -326,6 +340,11 @@ public SingleRedisResult(RedisValue value)
this.value = value; this.value = value;
} }
public override bool IsNull
{
get { return value.IsNull; }
}
public override string ToString() { return value.ToString(); } public override string ToString() { return value.ToString(); }
internal override bool AsBoolean() { return (bool)value; } internal override bool AsBoolean() { return (bool)value; }
......
...@@ -86,6 +86,22 @@ internal override Task<T> ExecuteAsync<T>(Message message, ResultProcessor<T> pr ...@@ -86,6 +86,22 @@ internal override Task<T> ExecuteAsync<T>(Message message, ResultProcessor<T> pr
// store it, and return the task of the *outer* command // store it, and return the task of the *outer* command
// (there is no task for the inner command) // (there is no task for the inner command)
(pending ?? (pending = new List<QueuedMessage>())).Add(queued); (pending ?? (pending = new List<QueuedMessage>())).Add(queued);
switch(message.Command)
{
case RedisCommand.EVAL:
case RedisCommand.EVALSHA:
// people can do very naughty things in an EVAL
// including change the DB; change it back to what we
// think it should be!
var sel = PhysicalConnection.GetSelectDatabaseCommand(message.Db);
queued = new QueuedMessage(sel);
wasQueued = ResultBox<bool>.Get(null);
queued.SetSource(wasQueued, QueuedProcessor.Default);
pending.Add(queued);
break;
}
return task; return task;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment