Commit 9fd3ec40 authored by Mårten Wikström's avatar Mårten Wikström

Merge pull request #1 from StackExchange/master

Merge new stuff
parents 92765c7c 1b3e0040
......@@ -9,7 +9,7 @@ or
> There doesn't seem to be a `Flush(...)` method? How can I remove all the keys in the database?
The key word here, oddly enough, is the last one: database. Because StackExchange.Redis aims to target scenarios such as cluster, it is important to know which commands target the *database* (the logical database that could be distributed over multiple nodes), and which commands target the *server*. The folliowing commands all target a single server:
The key word here, oddly enough, is the last one: database. Because StackExchange.Redis aims to target scenarios such as cluster, it is important to know which commands target the *database* (the logical database that could be distributed over multiple nodes), and which commands target the *server*. The following commands all target a single server:
- `KEYS` / `SCAN`
- `FLUSHDB` / `FLUSHALL`
......
......@@ -107,6 +107,7 @@
<Compile Include="TestInfoReplicationChecks.cs" />
<Compile Include="Transactions.cs" />
<Compile Include="VPNTest.cs" />
<Compile Include="WithKeyPrefixTests.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
......
using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation;
using System;
namespace StackExchange.Redis.Tests
{
[TestFixture]
public class WithKeyPrefixTests : TestBase
{
[Test]
public void BlankPrefixYieldsSame_Bytes()
{
using (var conn = Create())
{
var raw = conn.GetDatabase(1);
var prefixed = raw.WithKeyPrefix(new byte[0]);
Assert.AreSame(raw, prefixed);
}
}
[Test]
public void BlankPrefixYieldsSame_String()
{
using (var conn = Create())
{
var raw = conn.GetDatabase(1);
var prefixed = raw.WithKeyPrefix("");
Assert.AreSame(raw, prefixed);
}
}
[Test, ExpectedException(typeof(ArgumentNullException))]
public void NullPrefixIsError_Bytes()
{
using (var conn = Create())
{
var raw = conn.GetDatabase(1);
var prefixed = raw.WithKeyPrefix((byte[])null);
}
}
[Test, ExpectedException(typeof(ArgumentNullException))]
public void NullPrefixIsError_String()
{
using (var conn = Create())
{
var raw = conn.GetDatabase(1);
var prefixed = raw.WithKeyPrefix((string)null);
}
}
[Test, ExpectedException(typeof(ArgumentNullException))]
[TestCase("abc")]
[TestCase("")]
[TestCase(null)]
public void NullDatabaseIsError(string prefix)
{
IDatabase raw = null;
var prefixed = raw.WithKeyPrefix(prefix);
}
[Test]
public void BasicSmokeTest()
{
using(var conn = Create())
{
var raw = conn.GetDatabase(1);
var foo = raw.WithKeyPrefix("foo");
var foobar = foo.WithKeyPrefix("bar");
string key = Me();
string s = Guid.NewGuid().ToString(), t = Guid.NewGuid().ToString();
foo.StringSet(key, s);
var val = (string)foo.StringGet(key);
Assert.AreEqual(s, val); // fooBasicSmokeTest
foobar.StringSet(key, t);
val = (string)foobar.StringGet(key);
Assert.AreEqual(t, val); // foobarBasicSmokeTest
val = (string)foo.StringGet("bar" + key);
Assert.AreEqual(t, val); // foobarBasicSmokeTest
val = (string)raw.StringGet("foo" + key);
Assert.AreEqual(s, val); // fooBasicSmokeTest
val = (string)raw.StringGet("foobar" + key);
Assert.AreEqual(t, val); // foobarBasicSmokeTest
}
}
[Test]
public void ConditionTest()
{
using(var conn = Create())
{
var raw = conn.GetDatabase(2);
var foo = raw.WithKeyPrefix("tran:");
raw.KeyDelete("tran:abc");
raw.KeyDelete("tran:i");
// execute while key exists
raw.StringSet("tran:abc", "def");
var tran = foo.CreateTransaction();
tran.AddCondition(Condition.KeyExists("abc"));
tran.StringIncrementAsync("i");
tran.Execute();
int i = (int)raw.StringGet("tran:i");
Assert.AreEqual(1, i);
// repeat without key
raw.KeyDelete("tran:abc");
tran = foo.CreateTransaction();
tran.AddCondition(Condition.KeyExists("abc"));
tran.StringIncrementAsync("i");
tran.Execute();
i = (int)raw.StringGet("tran:i");
Assert.AreEqual(1, i);
}
}
}
}
......@@ -9,6 +9,7 @@ namespace StackExchange.Redis
/// </summary>
public abstract class Condition
{
internal abstract Condition MapKeys(Func<RedisKey,RedisKey> map);
private Condition() { }
......@@ -144,12 +145,14 @@ internal override void WriteImpl(PhysicalConnection physical)
internal class ExistsCondition : Condition
{
internal readonly bool expectedResult;
private readonly bool expectedResult;
private readonly RedisValue hashField;
private readonly RedisKey key;
internal readonly RedisValue hashField;
internal readonly RedisKey key;
internal override Condition MapKeys(Func<RedisKey,RedisKey> map)
{
return new ExistsCondition(map(key), hashField, expectedResult);
}
public ExistsCondition(RedisKey key, RedisValue hashField, bool expectedResult)
{
if (key.IsNull) throw new ArgumentException("key");
......@@ -198,10 +201,15 @@ internal override bool TryValidate(RawResult result, out bool value)
}
internal class EqualsCondition : Condition
{
internal readonly bool expectedEqual;
internal readonly RedisValue hashField, expectedValue;
internal readonly RedisKey key;
{
internal override Condition MapKeys(Func<RedisKey,RedisKey> map)
{
return new EqualsCondition(map(key), hashField, expectedEqual, expectedValue);
}
private readonly bool expectedEqual;
private readonly RedisValue hashField, expectedValue;
private readonly RedisKey key;
public EqualsCondition(RedisKey key, RedisValue hashField, bool expectedEqual, RedisValue expectedValue)
{
if (key.IsNull) throw new ArgumentException("key");
......
......@@ -808,7 +808,7 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <summary>
/// Return the position of the first bit set to 1 or 0 in a string.
/// The position is returned thinking at the string as an array of bits from left to right where the first byte most significant bit is at position 0, the second byte most significant big is at position 8 and so forth.
/// The position is returned thinking at the string as an array of bits from left to right where the first byte most significant bit is at position 0, the second byte most significant bit is at position 8 and so forth.
/// An start and end may be specified; these are in bytes, not bits; start and end can contain negative values in order to index bytes starting from the end of the string, where -1 is the last byte, -2 is the penultimate, and so forth.
/// </summary>
/// <returns>The command returns the position of the first bit set to 1 or 0 according to the request.
......@@ -922,4 +922,4 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>http://redis.io/commands/setrange</remarks>
RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None);
}
}
\ No newline at end of file
}
......@@ -5,7 +5,7 @@ namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation
/// <summary>
/// Provides the <see cref="WithKeyPrefix"/> extension method to <see cref="IDatabase"/>.
/// </summary>
public static class DatabaseExtension
public static class DatabaseExtensions
{
/// <summary>
/// Creates a new <see cref="IDatabase"/> instance that provides an isolated key space
......@@ -44,9 +44,22 @@ public static IDatabase WithKeyPrefix(this IDatabase database, RedisKey keyPrefi
throw new ArgumentNullException("database");
}
if (keyPrefix.IsNull || keyPrefix.Value.Length == 0)
if (keyPrefix.IsNull)
{
throw new ArgumentException("The specified prefix cannot be null or empty", "keyPrefix");
throw new ArgumentNullException("keyPrefix");
}
if (keyPrefix.Value.Length == 0)
{
return database; // fine - you can keep using the original, then
}
if(database is DatabaseWrapper)
{
// combine the key in advance to minimize indirection
var wrapper = (DatabaseWrapper)database;
keyPrefix = wrapper.ToInner(keyPrefix);
database = wrapper.Inner;
}
return new DatabaseWrapper(database, keyPrefix);
......
......@@ -623,6 +623,8 @@ public IEnumerable<SortedSetEntry> SortedSetScan(RedisKey key, RedisValue patter
return this.Inner.SortedSetScan(this.ToInner(key), pattern, pageSize, flags);
}
#if DEBUG
public string ClientGetName(CommandFlags flags = CommandFlags.None)
{
return this.Inner.ClientGetName(flags);
......@@ -632,5 +634,6 @@ public void Quit(CommandFlags flags = CommandFlags.None)
{
this.Inner.Quit(flags);
}
#endif
}
}
......@@ -12,7 +12,7 @@ public TransactionWrapper(ITransaction inner, RedisKey prefix)
public ConditionResult AddCondition(Condition condition)
{
return this.Inner.AddCondition(this.ToInner(condition));
return this.Inner.AddCondition(condition == null ? null : condition.MapKeys(GetMapFunction()));
}
public bool Execute(CommandFlags flags = CommandFlags.None)
......
......@@ -644,7 +644,7 @@ public Task<string> ClientGetNameAsync(CommandFlags flags = CommandFlags.None)
return this.Inner.ClientGetNameAsync(flags);
}
protected RedisKey ToInner(RedisKey outer)
protected internal RedisKey ToInner(RedisKey outer)
{
return this.Prefix + outer;
}
......@@ -754,44 +754,14 @@ protected RedisValue[] SortGetToInner(RedisValue[] outer)
protected RedisChannel ToInner(RedisChannel outer)
{
return this.Prefix + outer;
}
protected Condition ToInner(Condition outer)
{
Condition.ExistsCondition asExists = outer as Condition.ExistsCondition;
if (asExists != null)
{
return this.ToInner(asExists);
}
Condition.EqualsCondition asEquals = outer as Condition.EqualsCondition;
if (asEquals != null)
{
return this.ToInner(asEquals);
}
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture,
"Unsupported condition: {0}", outer));
}
private Condition.ExistsCondition ToInner(Condition.ExistsCondition outer)
{
return new Condition.ExistsCondition(
this.ToInner(outer.key),
outer.hashField,
outer.expectedResult);
return RedisKey.Concatenate((byte[])Prefix, (byte[])outer);
}
private Condition.EqualsCondition ToInner(Condition.EqualsCondition outer)
private Func<RedisKey, RedisKey> mapFunction;
protected Func<RedisKey, RedisKey> GetMapFunction()
{
return new Condition.EqualsCondition(
this.ToInner(outer.key),
outer.hashField,
outer.expectedEqual,
outer.expectedValue);
// create as a delegate when first required, then re-use
return mapFunction ?? (mapFunction = new Func<RedisKey, RedisKey>(this.ToInner));
}
}
}
......@@ -195,5 +195,29 @@ internal void AssertNotNull()
return BitConverter.ToString(arr);
}
}
/// <summary>
/// Concatenate two keys
/// </summary>
public static RedisKey operator +(RedisKey x, RedisKey y)
{
return Concatenate(x.value, y.value);
}
internal static byte[] Concatenate(byte[] x, byte[] y)
{
// either null? yield the other; note this includes the "both null becomes null" case
if (x == null) return y;
if (y == null) return x;
// either empty? yield the other
if (x.Length == 0) return y;
if (y.Length == 0) return x;
byte[] result = new byte[x.Length + y.Length];
Buffer.BlockCopy(x, 0, result, 0, x.Length);
Buffer.BlockCopy(y, 0, result, x.Length, y.Length);
return result;
}
}
}
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