Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
StackExchange.Redis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
tsai
StackExchange.Redis
Commits
c002a00e
Commit
c002a00e
authored
Mar 28, 2014
by
Marc Gravell
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
migrate BookSleeve "Scripting" suite; fix "eval inside transaction" bug
parent
6c191d56
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
431 additions
and
331 deletions
+431
-331
Program.cs
MigratedBookSleeveTestSuite/Program.cs
+5
-5
Scripting.cs
MigratedBookSleeveTestSuite/Scripting.cs
+374
-322
PhysicalConnection.cs
...kExchange.Redis/StackExchange/Redis/PhysicalConnection.cs
+17
-4
RedisResult.cs
StackExchange.Redis/StackExchange/Redis/RedisResult.cs
+19
-0
RedisTransaction.cs
StackExchange.Redis/StackExchange/Redis/RedisTransaction.cs
+16
-0
No files found.
MigratedBookSleeveTestSuite/Program.cs
View file @
c002a00e
...
@@ -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
}
}
}
}
...
...
MigratedBookSleeveTestSuite/Scripting.cs
View file @
c002a00e
//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
));
}
}
}
}
StackExchange.Redis/StackExchange/Redis/PhysicalConnection.cs
View file @
c002a00e
...
@@ -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
()
{
{
...
...
StackExchange.Redis/StackExchange/Redis/RedisResult.cs
View file @
c002a00e
...
@@ -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
;
}
...
...
StackExchange.Redis/StackExchange/Redis/RedisTransaction.cs
View file @
c002a00e
...
@@ -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
;
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment