Commit 1a51bc11 authored by Marc Gravell's avatar Marc Gravell

add TypedRedisValue - works a lot like RedisResult, but: value-typed; helps perf a lot

parent 40c5d21d
...@@ -110,7 +110,8 @@ protected override long Llen(int database, RedisKey key) ...@@ -110,7 +110,8 @@ protected override long Llen(int database, RedisKey key)
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
static void ThrowArgumentOutOfRangeException() => throw new ArgumentOutOfRangeException(); static void ThrowArgumentOutOfRangeException() => throw new ArgumentOutOfRangeException();
protected override void LRange(int database, RedisKey key, long start, RedisValue[] arr)
protected override void LRange(int database, RedisKey key, long start, Span<TypedRedisValue> arr)
{ {
var stack = GetStack(key, false); var stack = GetStack(key, false);
...@@ -123,7 +124,7 @@ protected override void LRange(int database, RedisKey key, long start, RedisValu ...@@ -123,7 +124,7 @@ protected override void LRange(int database, RedisKey key, long start, RedisValu
for (int i = 0; i < arr.Length; i++) for (int i = 0; i < arr.Length; i++)
{ {
if (!iter.MoveNext()) ThrowArgumentOutOfRangeException(); if (!iter.MoveNext()) ThrowArgumentOutOfRangeException();
arr[i] = iter.Current; arr[i] = TypedRedisValue.BulkString(iter.Current);
} }
} }
} }
......
...@@ -14,16 +14,13 @@ namespace StackExchange.Redis.Server ...@@ -14,16 +14,13 @@ namespace StackExchange.Redis.Server
public override string ToString() => Command; public override string ToString() => Command;
public override bool Equals(object obj) => throw new NotSupportedException(); public override bool Equals(object obj) => throw new NotSupportedException();
public RedisResult WrongArgCount() => RedisResult.Create($"ERR wrong number of arguments for '{Command}' command", ResultType.Error); public TypedRedisValue WrongArgCount() => TypedRedisValue.Error($"ERR wrong number of arguments for '{Command}' command");
public RedisResult UnknownSubcommandOrArgumentCount() => RedisResult.Create($"ERR Unknown subcommand or wrong number of arguments for '{Command}'.", ResultType.Error); public TypedRedisValue UnknownSubcommandOrArgumentCount() => TypedRedisValue.Error($"ERR Unknown subcommand or wrong number of arguments for '{Command}'.");
public string GetString(int index) public string GetString(int index)
=> _inner[index].GetString(); => _inner[index].GetString();
internal RedisResult GetResult(int index)
=> RedisResult.Create(_inner[index].AsRedisValue());
public bool IsString(int index, string value) // TODO: optimize public bool IsString(int index, string value) // TODO: optimize
=> string.Equals(value, _inner[index].GetString(), StringComparison.OrdinalIgnoreCase); => string.Equals(value, _inner[index].GetString(), StringComparison.OrdinalIgnoreCase);
......
...@@ -42,7 +42,7 @@ protected override void AppendStats(StringBuilder sb) ...@@ -42,7 +42,7 @@ protected override void AppendStats(StringBuilder sb)
public int Databases { get; } public int Databases { get; }
[RedisCommand(-3)] [RedisCommand(-3)]
protected virtual RedisResult Sadd(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Sadd(RedisClient client, RedisRequest request)
{ {
int added = 0; int added = 0;
var key = request.GetKey(1); var key = request.GetKey(1);
...@@ -51,12 +51,12 @@ protected virtual RedisResult Sadd(RedisClient client, RedisRequest request) ...@@ -51,12 +51,12 @@ protected virtual RedisResult Sadd(RedisClient client, RedisRequest request)
if (Sadd(client.Database, key, request.GetValue(i))) if (Sadd(client.Database, key, request.GetValue(i)))
added++; added++;
} }
return RedisResult.Create(added, ResultType.Integer); return TypedRedisValue.Integer(added);
} }
protected virtual bool Sadd(int database, RedisKey key, RedisValue value) => throw new NotSupportedException(); protected virtual bool Sadd(int database, RedisKey key, RedisValue value) => throw new NotSupportedException();
[RedisCommand(-3)] [RedisCommand(-3)]
protected virtual RedisResult Srem(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Srem(RedisClient client, RedisRequest request)
{ {
int removed = 0; int removed = 0;
var key = request.GetKey(1); var key = request.GetKey(1);
...@@ -65,55 +65,55 @@ protected virtual RedisResult Srem(RedisClient client, RedisRequest request) ...@@ -65,55 +65,55 @@ protected virtual RedisResult Srem(RedisClient client, RedisRequest request)
if (Srem(client.Database, key, request.GetValue(i))) if (Srem(client.Database, key, request.GetValue(i)))
removed++; removed++;
} }
return RedisResult.Create(removed, ResultType.Integer); return TypedRedisValue.Integer(removed);
} }
protected virtual bool Srem(int database, RedisKey key, RedisValue value) => throw new NotSupportedException(); protected virtual bool Srem(int database, RedisKey key, RedisValue value) => throw new NotSupportedException();
[RedisCommand(2)] [RedisCommand(2)]
protected virtual RedisResult Spop(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Spop(RedisClient client, RedisRequest request)
=> RedisResult.Create(Spop(client.Database, request.GetKey(1)), ResultType.BulkString); => TypedRedisValue.BulkString(Spop(client.Database, request.GetKey(1)));
protected virtual RedisValue Spop(int database, RedisKey key) => throw new NotSupportedException(); protected virtual RedisValue Spop(int database, RedisKey key) => throw new NotSupportedException();
[RedisCommand(2)] [RedisCommand(2)]
protected virtual RedisResult Scard(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Scard(RedisClient client, RedisRequest request)
=> RedisResult.Create(Scard(client.Database, request.GetKey(1)), ResultType.Integer); => TypedRedisValue.Integer(Scard(client.Database, request.GetKey(1)));
protected virtual long Scard(int database, RedisKey key) => throw new NotSupportedException(); protected virtual long Scard(int database, RedisKey key) => throw new NotSupportedException();
[RedisCommand(3)] [RedisCommand(3)]
protected virtual RedisResult Sismember(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Sismember(RedisClient client, RedisRequest request)
=> Sismember(client.Database, request.GetKey(1), request.GetValue(2)) ? RedisResult.One : RedisResult.Zero; => Sismember(client.Database, request.GetKey(1), request.GetValue(2)) ? TypedRedisValue.One : TypedRedisValue.Zero;
protected virtual bool Sismember(int database, RedisKey key, RedisValue value) => throw new NotSupportedException(); protected virtual bool Sismember(int database, RedisKey key, RedisValue value) => throw new NotSupportedException();
[RedisCommand(3, "client", "setname", LockFree = true)] [RedisCommand(3, "client", "setname", LockFree = true)]
protected virtual RedisResult ClientSetname(RedisClient client, RedisRequest request) protected virtual TypedRedisValue ClientSetname(RedisClient client, RedisRequest request)
{ {
client.Name = request.GetString(2); client.Name = request.GetString(2);
return RedisResult.OK; return TypedRedisValue.OK;
} }
[RedisCommand(2, "client", "getname", LockFree = true)] [RedisCommand(2, "client", "getname", LockFree = true)]
protected virtual RedisResult ClientGetname(RedisClient client, RedisRequest request) protected virtual TypedRedisValue ClientGetname(RedisClient client, RedisRequest request)
=> RedisResult.Create(client.Name, ResultType.BulkString); => TypedRedisValue.BulkString(client.Name);
[RedisCommand(3, "client", "reply", LockFree = true)] [RedisCommand(3, "client", "reply", LockFree = true)]
protected virtual RedisResult ClientReply(RedisClient client, RedisRequest request) protected virtual TypedRedisValue ClientReply(RedisClient client, RedisRequest request)
{ {
if (request.IsString(2, "on")) client.SkipReplies = -1; // reply to nothing if (request.IsString(2, "on")) client.SkipReplies = -1; // reply to nothing
else if (request.IsString(2, "off")) client.SkipReplies = 0; // reply to everything else if (request.IsString(2, "off")) client.SkipReplies = 0; // reply to everything
else if (request.IsString(2, "skip")) client.SkipReplies = 2; // this one, and the next one else if (request.IsString(2, "skip")) client.SkipReplies = 2; // this one, and the next one
else return RedisResult.Create("ERR syntax error", ResultType.Error); else return TypedRedisValue.Error("ERR syntax error");
return RedisResult.OK; return TypedRedisValue.OK;
} }
[RedisCommand(-1)] [RedisCommand(-1)]
protected virtual RedisResult Cluster(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Cluster(RedisClient client, RedisRequest request)
=> CommandNotFound(request.Command); => CommandNotFound(request.Command);
[RedisCommand(-3)] [RedisCommand(-3)]
protected virtual RedisResult Lpush(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Lpush(RedisClient client, RedisRequest request)
{ {
var key = request.GetKey(1); var key = request.GetKey(1);
long length = -1; long length = -1;
...@@ -121,11 +121,11 @@ protected virtual RedisResult Lpush(RedisClient client, RedisRequest request) ...@@ -121,11 +121,11 @@ protected virtual RedisResult Lpush(RedisClient client, RedisRequest request)
{ {
length = Lpush(client.Database, key, request.GetValue(i)); length = Lpush(client.Database, key, request.GetValue(i));
} }
return RedisResult.Create(length, ResultType.Integer); return TypedRedisValue.Integer(length);
} }
[RedisCommand(-3)] [RedisCommand(-3)]
protected virtual RedisResult Rpush(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Rpush(RedisClient client, RedisRequest request)
{ {
var key = request.GetKey(1); var key = request.GetKey(1);
long length = -1; long length = -1;
...@@ -133,20 +133,20 @@ protected virtual RedisResult Rpush(RedisClient client, RedisRequest request) ...@@ -133,20 +133,20 @@ protected virtual RedisResult Rpush(RedisClient client, RedisRequest request)
{ {
length = Rpush(client.Database, key, request.GetValue(i)); length = Rpush(client.Database, key, request.GetValue(i));
} }
return RedisResult.Create(length, ResultType.Integer); return TypedRedisValue.Integer(length);
} }
[RedisCommand(2)] [RedisCommand(2)]
protected virtual RedisResult Lpop(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Lpop(RedisClient client, RedisRequest request)
=> RedisResult.Create(Lpop(client.Database, request.GetKey(1)), ResultType.BulkString); => TypedRedisValue.BulkString(Lpop(client.Database, request.GetKey(1)));
[RedisCommand(2)] [RedisCommand(2)]
protected virtual RedisResult Rpop(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Rpop(RedisClient client, RedisRequest request)
=> RedisResult.Create(Rpop(client.Database, request.GetKey(1)), ResultType.BulkString); => TypedRedisValue.BulkString(Rpop(client.Database, request.GetKey(1)));
[RedisCommand(2)] [RedisCommand(2)]
protected virtual RedisResult Llen(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Llen(RedisClient client, RedisRequest request)
=> RedisResult.Create(Llen(client.Database, request.GetKey(1)), ResultType.Integer); => TypedRedisValue.Integer(Llen(client.Database, request.GetKey(1)));
protected virtual long Lpush(int database, RedisKey key, RedisValue value) => throw new NotSupportedException(); protected virtual long Lpush(int database, RedisKey key, RedisValue value) => throw new NotSupportedException();
protected virtual long Rpush(int database, RedisKey key, RedisValue value) => throw new NotSupportedException(); protected virtual long Rpush(int database, RedisKey key, RedisValue value) => throw new NotSupportedException();
...@@ -155,18 +155,18 @@ protected virtual RedisResult Llen(RedisClient client, RedisRequest request) ...@@ -155,18 +155,18 @@ protected virtual RedisResult Llen(RedisClient client, RedisRequest request)
protected virtual RedisValue Lpop(int database, RedisKey key) => throw new NotSupportedException(); protected virtual RedisValue Lpop(int database, RedisKey key) => throw new NotSupportedException();
[RedisCommand(4)] [RedisCommand(4)]
protected virtual RedisResult LRange(RedisClient client, RedisRequest request) protected virtual TypedRedisValue LRange(RedisClient client, RedisRequest request)
{ {
var key = request.GetKey(1); var key = request.GetKey(1);
long start = request.GetInt64(2), stop = request.GetInt64(3); long start = request.GetInt64(2), stop = request.GetInt64(3);
var len = Llen(client.Database, key); var len = Llen(client.Database, key);
if (len == 0) return RedisResult.EmptyArray; if (len == 0) return TypedRedisValue.EmptyArray;
if (start < 0) start = len + start; if (start < 0) start = len + start;
if (stop < 0) stop = len + stop; if (stop < 0) stop = len + stop;
if (stop < 0 || start >= len || stop < start) return RedisResult.EmptyArray; if (stop < 0 || start >= len || stop < start) return TypedRedisValue.EmptyArray;
if (start < 0) start = 0; if (start < 0) start = 0;
else if (start >= len) start = len - 1; else if (start >= len) start = len - 1;
...@@ -174,11 +174,11 @@ protected virtual RedisResult LRange(RedisClient client, RedisRequest request) ...@@ -174,11 +174,11 @@ protected virtual RedisResult LRange(RedisClient client, RedisRequest request)
if (stop < 0) stop = 0; if (stop < 0) stop = 0;
else if (stop >= len) stop = len - 1; else if (stop >= len) stop = len - 1;
var arr = new RedisValue[(stop - start) + 1]; var arr = TypedRedisValue.Rent(checked((int)((stop - start) + 1)));
LRange(client.Database, key, start, arr); LRange(client.Database, key, start, arr.MutableSpan);
return RedisResult.Create(arr); return arr;
} }
protected virtual void LRange(int database, RedisKey key, long start, RedisValue[] arr) => throw new NotSupportedException(); protected virtual void LRange(int database, RedisKey key, long start, Span<TypedRedisValue> arr) => throw new NotSupportedException();
protected virtual void OnUpdateServerConfiguration() { } protected virtual void OnUpdateServerConfiguration() { }
protected RedisConfig ServerConfiguration { get; } = RedisConfig.Create(); protected RedisConfig ServerConfiguration { get; } = RedisConfig.Create();
...@@ -212,35 +212,35 @@ internal int CountMatch(string pattern) ...@@ -212,35 +212,35 @@ internal int CountMatch(string pattern)
} }
} }
[RedisCommand(3, "config", "get", LockFree = true)] [RedisCommand(3, "config", "get", LockFree = true)]
protected virtual RedisResult Config(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Config(RedisClient client, RedisRequest request)
{ {
var pattern = request.GetString(2); var pattern = request.GetString(2);
OnUpdateServerConfiguration(); OnUpdateServerConfiguration();
var config = ServerConfiguration; var config = ServerConfiguration;
var matches = config.CountMatch(pattern); var matches = config.CountMatch(pattern);
if (matches == 0) return RedisResult.Create(Array.Empty<RedisResult>()); if (matches == 0) return TypedRedisValue.EmptyArray;
var arr = new RedisResult[2 * matches]; var arr = TypedRedisValue.Rent(2 * matches);
int index = 0; int index = 0;
foreach (var pair in config.Wrapped) foreach (var pair in config.Wrapped)
{ {
if (IsMatch(pattern, pair.Key)) if (IsMatch(pattern, pair.Key))
{ {
arr[index++] = RedisResult.Create(pair.Key, ResultType.BulkString); arr[index++] = TypedRedisValue.BulkString(pair.Key);
arr[index++] = RedisResult.Create(pair.Value, ResultType.BulkString); arr[index++] = TypedRedisValue.BulkString(pair.Value);
} }
} }
if (index != arr.Length) throw new InvalidOperationException("Configuration CountMatch fail"); if (index != arr.Length) throw new InvalidOperationException("Configuration CountMatch fail");
return RedisResult.Create(arr); return arr;
} }
[RedisCommand(2, LockFree = true)] [RedisCommand(2, LockFree = true)]
protected virtual RedisResult Echo(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Echo(RedisClient client, RedisRequest request)
=> request.GetResult(1); => TypedRedisValue.BulkString(request.GetValue(1));
[RedisCommand(2)] [RedisCommand(2)]
protected virtual RedisResult Exists(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Exists(RedisClient client, RedisRequest request)
{ {
int count = 0; int count = 0;
var db = client.Database; var db = client.Database;
...@@ -249,7 +249,7 @@ protected virtual RedisResult Exists(RedisClient client, RedisRequest request) ...@@ -249,7 +249,7 @@ protected virtual RedisResult Exists(RedisClient client, RedisRequest request)
if (Exists(db, request.GetKey(i))) if (Exists(db, request.GetKey(i)))
count++; count++;
} }
return RedisResult.Create(count, ResultType.Integer); return TypedRedisValue.Integer(count);
} }
protected virtual bool Exists(int database, RedisKey key) protected virtual bool Exists(int database, RedisKey key)
...@@ -262,32 +262,32 @@ protected virtual bool Exists(int database, RedisKey key) ...@@ -262,32 +262,32 @@ protected virtual bool Exists(int database, RedisKey key)
} }
[RedisCommand(2)] [RedisCommand(2)]
protected virtual RedisResult Get(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Get(RedisClient client, RedisRequest request)
=> RedisResult.Create(Get(client.Database, request.GetKey(1)), ResultType.BulkString); => TypedRedisValue.BulkString(Get(client.Database, request.GetKey(1)));
protected virtual RedisValue Get(int database, RedisKey key) => throw new NotSupportedException(); protected virtual RedisValue Get(int database, RedisKey key) => throw new NotSupportedException();
[RedisCommand(3)] [RedisCommand(3)]
protected virtual RedisResult Set(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Set(RedisClient client, RedisRequest request)
{ {
Set(client.Database, request.GetKey(1), request.GetValue(2)); Set(client.Database, request.GetKey(1), request.GetValue(2));
return RedisResult.OK; return TypedRedisValue.OK;
} }
protected virtual void Set(int database, RedisKey key, RedisValue value) => throw new NotSupportedException(); protected virtual void Set(int database, RedisKey key, RedisValue value) => throw new NotSupportedException();
[RedisCommand(1)] [RedisCommand(1)]
protected new virtual RedisResult Shutdown(RedisClient client, RedisRequest request) protected new virtual TypedRedisValue Shutdown(RedisClient client, RedisRequest request)
{ {
DoShutdown(); DoShutdown();
return RedisResult.OK; return TypedRedisValue.OK;
} }
[RedisCommand(2)] [RedisCommand(2)]
protected virtual RedisResult Strlen(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Strlen(RedisClient client, RedisRequest request)
=> RedisResult.Create(Strlen(client.Database, request.GetKey(1)), ResultType.Integer); => TypedRedisValue.Integer(Strlen(client.Database, request.GetKey(1)));
protected virtual long Strlen(int database, RedisKey key) => Get(database, key).Length(); protected virtual long Strlen(int database, RedisKey key) => Get(database, key).Length();
[RedisCommand(-2)] [RedisCommand(-2)]
protected virtual RedisResult Del(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Del(RedisClient client, RedisRequest request)
{ {
int count = 0; int count = 0;
for (int i = 1; i < request.Count; i++) for (int i = 1; i < request.Count; i++)
...@@ -295,40 +295,40 @@ protected virtual RedisResult Del(RedisClient client, RedisRequest request) ...@@ -295,40 +295,40 @@ protected virtual RedisResult Del(RedisClient client, RedisRequest request)
if (Del(client.Database, request.GetKey(i))) if (Del(client.Database, request.GetKey(i)))
count++; count++;
} }
return RedisResult.Create(count, ResultType.Integer); return TypedRedisValue.Integer(count);
} }
protected virtual bool Del(int database, RedisKey key) => throw new NotSupportedException(); protected virtual bool Del(int database, RedisKey key) => throw new NotSupportedException();
[RedisCommand(1)] [RedisCommand(1)]
protected virtual RedisResult Dbsize(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Dbsize(RedisClient client, RedisRequest request)
=> RedisResult.Create(Dbsize(client.Database), ResultType.Integer); => TypedRedisValue.Integer(Dbsize(client.Database));
protected virtual long Dbsize(int database) => throw new NotSupportedException(); protected virtual long Dbsize(int database) => throw new NotSupportedException();
[RedisCommand(1)] [RedisCommand(1)]
protected virtual RedisResult Flushall(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Flushall(RedisClient client, RedisRequest request)
{ {
var count = Databases; var count = Databases;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
Flushdb(i); Flushdb(i);
} }
return RedisResult.OK; return TypedRedisValue.OK;
} }
[RedisCommand(1)] [RedisCommand(1)]
protected virtual RedisResult Flushdb(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Flushdb(RedisClient client, RedisRequest request)
{ {
Flushdb(client.Database); Flushdb(client.Database);
return RedisResult.OK; return TypedRedisValue.OK;
} }
protected virtual void Flushdb(int database) => throw new NotSupportedException(); protected virtual void Flushdb(int database) => throw new NotSupportedException();
[RedisCommand(-1, LockFree = true, MaxArgs = 2)] [RedisCommand(-1, LockFree = true, MaxArgs = 2)]
protected virtual RedisResult Info(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Info(RedisClient client, RedisRequest request)
{ {
var info = Info(request.Count == 1 ? null : request.GetString(1)); var info = Info(request.Count == 1 ? null : request.GetString(1));
return RedisResult.Create(info, ResultType.BulkString); return TypedRedisValue.BulkString(info);
} }
protected virtual string Info(string selected) protected virtual string Info(string selected)
{ {
...@@ -346,16 +346,16 @@ bool IsMatch(string section) => string.IsNullOrWhiteSpace(selected) ...@@ -346,16 +346,16 @@ bool IsMatch(string section) => string.IsNullOrWhiteSpace(selected)
} }
[RedisCommand(2)] [RedisCommand(2)]
protected virtual RedisResult Keys(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Keys(RedisClient client, RedisRequest request)
{ {
List<RedisResult> found = null; List<TypedRedisValue> found = null;
foreach (var key in Keys(client.Database, request.GetKey(1))) foreach (var key in Keys(client.Database, request.GetKey(1)))
{ {
if (found == null) found = new List<RedisResult>(); if (found == null) found = new List<TypedRedisValue>();
found.Add(RedisResult.Create(key)); found.Add(TypedRedisValue.BulkString(key.AsRedisValue()));
} }
return RedisResult.Create( if (found == null) return TypedRedisValue.EmptyArray;
found == null ? Array.Empty<RedisResult>() : found.ToArray()); return TypedRedisValue.MultiBulk(found.ToArray());
} }
protected virtual IEnumerable<RedisKey> Keys(int database, RedisKey pattern) => throw new NotSupportedException(); protected virtual IEnumerable<RedisKey> Keys(int database, RedisKey pattern) => throw new NotSupportedException();
...@@ -401,25 +401,25 @@ StringBuilder AddHeader() ...@@ -401,25 +401,25 @@ StringBuilder AddHeader()
} }
} }
[RedisCommand(2, "memory", "purge")] [RedisCommand(2, "memory", "purge")]
protected virtual RedisResult MemoryPurge(RedisClient client, RedisRequest request) protected virtual TypedRedisValue MemoryPurge(RedisClient client, RedisRequest request)
{ {
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
return RedisResult.OK; return TypedRedisValue.OK;
} }
[RedisCommand(-2)] [RedisCommand(-2)]
protected virtual RedisResult Mget(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Mget(RedisClient client, RedisRequest request)
{ {
int argCount = request.Count; int argCount = request.Count;
var arr = new RedisResult[argCount - 1]; var arr = TypedRedisValue.Rent(argCount - 1);
var db = client.Database; var db = client.Database;
for (int i = 1; i < argCount; i++) for (int i = 1; i < argCount; i++)
{ {
arr[i - 1] = RedisResult.Create(Get(db, request.GetKey(i)), ResultType.BulkString); arr[i - 1] = TypedRedisValue.BulkString(Get(db, request.GetKey(i)));
} }
return RedisResult.Create(arr); return arr;
} }
[RedisCommand(-3)] [RedisCommand(-3)]
protected virtual RedisResult Mset(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Mset(RedisClient client, RedisRequest request)
{ {
int argCount = request.Count; int argCount = request.Count;
var db = client.Database; var db = client.Database;
...@@ -427,51 +427,50 @@ protected virtual RedisResult Mset(RedisClient client, RedisRequest request) ...@@ -427,51 +427,50 @@ protected virtual RedisResult Mset(RedisClient client, RedisRequest request)
{ {
Set(db, request.GetKey(i++), request.GetValue(i++)); Set(db, request.GetKey(i++), request.GetValue(i++));
} }
return RedisResult.OK; return TypedRedisValue.OK;
} }
[RedisCommand(-1, LockFree = true, MaxArgs = 2)] [RedisCommand(-1, LockFree = true, MaxArgs = 2)]
protected virtual RedisResult Ping(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Ping(RedisClient client, RedisRequest request)
=> RedisResult.Create(request.Count == 1 ? "PONG" : request.GetString(1), ResultType.SimpleString); => TypedRedisValue.SimpleString(request.Count == 1 ? "PONG" : request.GetString(1));
[RedisCommand(1, LockFree = true)] [RedisCommand(1, LockFree = true)]
protected virtual RedisResult Quit(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Quit(RedisClient client, RedisRequest request)
{ {
RemoveClient(client); RemoveClient(client);
return RedisResult.OK; return TypedRedisValue.OK;
} }
[RedisCommand(1, LockFree = true)] [RedisCommand(1, LockFree = true)]
protected virtual RedisResult Role(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Role(RedisClient client, RedisRequest request)
{ {
return RedisResult.Create(new[] var arr = TypedRedisValue.Rent(3);
{ arr[0] = TypedRedisValue.BulkString("master");
RedisResult.Create("master", ResultType.BulkString), arr[1] = TypedRedisValue.Integer(0);
RedisResult.Create(0, ResultType.Integer), arr[2] = TypedRedisValue.EmptyArray;
RedisResult.Create(Array.Empty<RedisResult>()) return arr;
});
} }
[RedisCommand(2, LockFree = true)] [RedisCommand(2, LockFree = true)]
protected virtual RedisResult Select(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Select(RedisClient client, RedisRequest request)
{ {
var raw = request.GetValue(1); var raw = request.GetValue(1);
if (!raw.IsInteger) return RedisResult.Create("ERR invalid DB index", ResultType.Error); if (!raw.IsInteger) return TypedRedisValue.Error("ERR invalid DB index");
int db = (int)raw; int db = (int)raw;
if (db < 0 || db >= Databases) return RedisResult.Create("ERR DB index is out of range", ResultType.Error); if (db < 0 || db >= Databases) return TypedRedisValue.Error("ERR DB index is out of range");
client.Database = db; client.Database = db;
return RedisResult.OK; return TypedRedisValue.OK;
} }
[RedisCommand(-2)] [RedisCommand(-2)]
protected virtual RedisResult Subscribe(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Subscribe(RedisClient client, RedisRequest request)
=> SubscribeImpl(client, request); => SubscribeImpl(client, request);
[RedisCommand(-2)] [RedisCommand(-2)]
protected virtual RedisResult Unsubscribe(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Unsubscribe(RedisClient client, RedisRequest request)
=> SubscribeImpl(client, request); => SubscribeImpl(client, request);
private RedisResult SubscribeImpl(RedisClient client, RedisRequest request) private TypedRedisValue SubscribeImpl(RedisClient client, RedisRequest request)
{ {
var reply = new RedisResult[3 * (request.Count - 1)]; var reply = TypedRedisValue.Rent(3 * (request.Count - 1));
int index = 0; int index = 0;
var mode = request.Command[0] == 'p' ? RedisChannel.PatternMode.Pattern : RedisChannel.PatternMode.Literal; var mode = request.Command[0] == 'p' ? RedisChannel.PatternMode.Pattern : RedisChannel.PatternMode.Literal;
for (int i = 1; i < request.Count; i++) for (int i = 1; i < request.Count; i++)
...@@ -482,44 +481,46 @@ private RedisResult SubscribeImpl(RedisClient client, RedisRequest request) ...@@ -482,44 +481,46 @@ private RedisResult SubscribeImpl(RedisClient client, RedisRequest request)
{ {
case "subscribe": count = client.Subscribe(channel); break; case "subscribe": count = client.Subscribe(channel); break;
case "unsubscribe": count = client.Unsubscribe(channel); break; case "unsubscribe": count = client.Unsubscribe(channel); break;
default: return null; default:
reply.Recycle(index);
return TypedRedisValue.Nil;
} }
reply[index++] = RedisResult.Create(request.Command, ResultType.BulkString); reply[index++] = TypedRedisValue.BulkString(request.Command);
reply[index++] = RedisResult.Create(channel); reply[index++] = TypedRedisValue.BulkString((byte[])channel);
reply[index++] = RedisResult.Create(count, ResultType.Integer); reply[index++] = TypedRedisValue.Integer(count);
} }
return RedisResult.Create(reply); return reply;
} }
static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
[RedisCommand(1, LockFree = true)] [RedisCommand(1, LockFree = true)]
protected virtual RedisResult Time(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Time(RedisClient client, RedisRequest request)
{ {
var delta = Time() - UnixEpoch; var delta = Time() - UnixEpoch;
var ticks = delta.Ticks; var ticks = delta.Ticks;
var seconds = ticks / TimeSpan.TicksPerSecond; var seconds = ticks / TimeSpan.TicksPerSecond;
var micros = (ticks % TimeSpan.TicksPerSecond) / (TimeSpan.TicksPerMillisecond / 1000); var micros = (ticks % TimeSpan.TicksPerSecond) / (TimeSpan.TicksPerMillisecond / 1000);
return RedisResult.Create(new[] { var reply = TypedRedisValue.Rent(2);
RedisResult.Create(seconds, ResultType.BulkString), reply[0] = TypedRedisValue.BulkString(seconds);
RedisResult.Create(micros, ResultType.BulkString), reply[1] = TypedRedisValue.BulkString(micros);
}); return reply;
} }
protected virtual DateTime Time() => DateTime.UtcNow; protected virtual DateTime Time() => DateTime.UtcNow;
[RedisCommand(-2)] [RedisCommand(-2)]
protected virtual RedisResult Unlink(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Unlink(RedisClient client, RedisRequest request)
=> Del(client, request); => Del(client, request);
[RedisCommand(2)] [RedisCommand(2)]
protected virtual RedisResult Incr(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Incr(RedisClient client, RedisRequest request)
=> RedisResult.Create(IncrBy(client.Database, request.GetKey(1), 1), ResultType.Integer); => TypedRedisValue.Integer(IncrBy(client.Database, request.GetKey(1), 1));
[RedisCommand(2)] [RedisCommand(2)]
protected virtual RedisResult Decr(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Decr(RedisClient client, RedisRequest request)
=> RedisResult.Create(IncrBy(client.Database, request.GetKey(1), -1), ResultType.Integer); => TypedRedisValue.Integer(IncrBy(client.Database, request.GetKey(1), -1));
[RedisCommand(3)] [RedisCommand(3)]
protected virtual RedisResult IncrBy(RedisClient client, RedisRequest request) protected virtual TypedRedisValue IncrBy(RedisClient client, RedisRequest request)
=> RedisResult.Create(IncrBy(client.Database, request.GetKey(1), request.GetInt64(2)), ResultType.Integer); => TypedRedisValue.Integer(IncrBy(client.Database, request.GetKey(1), request.GetInt64(2)));
protected virtual long IncrBy(int database, RedisKey key, long delta) protected virtual long IncrBy(int database, RedisKey key, long delta)
{ {
......
...@@ -30,7 +30,7 @@ public RespServer(TextWriter output = null) ...@@ -30,7 +30,7 @@ public RespServer(TextWriter output = null)
{ {
RedisCommandAttribute CheckSignatureAndGetAttribute(MethodInfo method) RedisCommandAttribute CheckSignatureAndGetAttribute(MethodInfo method)
{ {
if (method.ReturnType != typeof(RedisResult)) return null; if (method.ReturnType != typeof(TypedRedisValue)) return null;
var p = method.GetParameters(); var p = method.GetParameters();
if (p.Length != 2 || p[0].ParameterType != typeof(RedisClient) || p[1].ParameterType != typeof(RedisRequest)) if (p.Length != 2 || p[0].ParameterType != typeof(RedisClient) || p[1].ParameterType != typeof(RedisRequest))
return null; return null;
...@@ -149,7 +149,7 @@ public RespCommand Resolve(in RedisRequest request) ...@@ -149,7 +149,7 @@ public RespCommand Resolve(in RedisRequest request)
} }
return this; return this;
} }
public RedisResult Execute(RedisClient client, RedisRequest request) public TypedRedisValue Execute(RedisClient client, RedisRequest request)
{ {
var args = request.Count; var args = request.Count;
if (!CheckArity(request.Count)) return IsSubCommand if (!CheckArity(request.Count)) return IsSubCommand
...@@ -175,7 +175,7 @@ internal int NetArity() ...@@ -175,7 +175,7 @@ internal int NetArity()
return varadic ? -minMagnitude : minMagnitude; return varadic ? -minMagnitude : minMagnitude;
} }
} }
delegate RedisResult RespOperation(RedisClient client, RedisRequest request); delegate TypedRedisValue RespOperation(RedisClient client, RedisRequest request);
protected int TcpPort() protected int TcpPort()
{ {
...@@ -348,7 +348,7 @@ private void Log(string message) ...@@ -348,7 +348,7 @@ private void Log(string message)
} }
static Encoder s_sharedEncoder; // swapped in/out to avoid alloc on the public WriteResponse API static Encoder s_sharedEncoder; // swapped in/out to avoid alloc on the public WriteResponse API
public static ValueTask WriteResponseAsync(RedisClient client, PipeWriter output, RedisResult response) public static ValueTask WriteResponseAsync(RedisClient client, PipeWriter output, TypedRedisValue value)
{ {
async ValueTask Awaited(ValueTask wwrite, Encoder eenc) async ValueTask Awaited(ValueTask wwrite, Encoder eenc)
{ {
...@@ -356,13 +356,13 @@ async ValueTask Awaited(ValueTask wwrite, Encoder eenc) ...@@ -356,13 +356,13 @@ async ValueTask Awaited(ValueTask wwrite, Encoder eenc)
Interlocked.Exchange(ref s_sharedEncoder, eenc); Interlocked.Exchange(ref s_sharedEncoder, eenc);
} }
var enc = Interlocked.Exchange(ref s_sharedEncoder, null) ?? Encoding.UTF8.GetEncoder(); var enc = Interlocked.Exchange(ref s_sharedEncoder, null) ?? Encoding.UTF8.GetEncoder();
var write = WriteResponseAsync(client, output, response, enc); var write = WriteResponseAsync(client, output, value, enc);
if (!write.IsCompletedSuccessfully) return Awaited(write, enc); if (!write.IsCompletedSuccessfully) return Awaited(write, enc);
Interlocked.Exchange(ref s_sharedEncoder, enc); Interlocked.Exchange(ref s_sharedEncoder, enc);
return default; return default;
} }
internal static async ValueTask WriteResponseAsync(RedisClient client, PipeWriter output, RedisResult response, Encoder encoder) internal static async ValueTask WriteResponseAsync(RedisClient client, PipeWriter output, TypedRedisValue value, Encoder encoder)
{ {
void WritePrefix(PipeWriter ooutput, char pprefix) void WritePrefix(PipeWriter ooutput, char pprefix)
{ {
...@@ -371,13 +371,13 @@ void WritePrefix(PipeWriter ooutput, char pprefix) ...@@ -371,13 +371,13 @@ void WritePrefix(PipeWriter ooutput, char pprefix)
ooutput.Advance(1); ooutput.Advance(1);
} }
if (response == null) return; // not actually a request (i.e. empty/whitespace request) if (value.IsNil) return; // not actually a request (i.e. empty/whitespace request)
if (client != null && client.ShouldSkipResponse()) return; // intentionally skipping the result if (client != null && client.ShouldSkipResponse()) return; // intentionally skipping the result
char prefix; char prefix;
switch (response.Type) switch (value.Type)
{ {
case ResultType.Integer: case ResultType.Integer:
PhysicalConnection.WriteInteger(output, (long)response); PhysicalConnection.WriteInteger(output, (long)value.AsRedisValue());
break; break;
case ResultType.Error: case ResultType.Error:
prefix = '-'; prefix = '-';
...@@ -386,30 +386,28 @@ void WritePrefix(PipeWriter ooutput, char pprefix) ...@@ -386,30 +386,28 @@ void WritePrefix(PipeWriter ooutput, char pprefix)
prefix = '+'; prefix = '+';
BasicMessage: BasicMessage:
WritePrefix(output, prefix); WritePrefix(output, prefix);
var val = (string)value.AsRedisValue();
var val = response.AsString();
var expectedLength = Encoding.UTF8.GetByteCount(val); var expectedLength = Encoding.UTF8.GetByteCount(val);
PhysicalConnection.WriteRaw(output, val, expectedLength, encoder); PhysicalConnection.WriteRaw(output, val, expectedLength, encoder);
PhysicalConnection.WriteCrlf(output); PhysicalConnection.WriteCrlf(output);
break; break;
case ResultType.BulkString: case ResultType.BulkString:
PhysicalConnection.WriteBulkString(response.AsRedisValue(), output, encoder); PhysicalConnection.WriteBulkString(value.AsRedisValue(), output, encoder);
break; break;
case ResultType.MultiBulk: case ResultType.MultiBulk:
if (response.IsNull) if (value.IsNullArray)
{ {
PhysicalConnection.WriteMultiBulkHeader(output, -1); PhysicalConnection.WriteMultiBulkHeader(output, -1);
} }
else else
{ {
var arr = (RedisResult[])response; var count = value.Length;
PhysicalConnection.WriteMultiBulkHeader(output, arr.Length); PhysicalConnection.WriteMultiBulkHeader(output, count);
for (int i = 0; i < arr.Length; i++) for (int i = 0; i < count ; i++)
{ {
var item = arr[i]; var item = value[i];
if (item == null) if (item.IsNil)
throw new InvalidOperationException("Array element cannot be null, index " + i); throw new InvalidOperationException("Array element cannot be nil, index " + i);
// note: don't pass client down; this would impact SkipReplies // note: don't pass client down; this would impact SkipReplies
await WriteResponseAsync(null, output, item, encoder); await WriteResponseAsync(null, output, item, encoder);
...@@ -418,7 +416,7 @@ void WritePrefix(PipeWriter ooutput, char pprefix) ...@@ -418,7 +416,7 @@ void WritePrefix(PipeWriter ooutput, char pprefix)
break; break;
default: default:
throw new InvalidOperationException( throw new InvalidOperationException(
"Unexpected result type: " + response.Type); "Unexpected result type: " + value.Type);
} }
await output.FlushAsync(); await output.FlushAsync();
} }
...@@ -438,18 +436,21 @@ public static bool TryParseRequest(ref ReadOnlySequence<byte> buffer, out RedisR ...@@ -438,18 +436,21 @@ public static bool TryParseRequest(ref ReadOnlySequence<byte> buffer, out RedisR
} }
public ValueTask<bool> TryProcessRequestAsync(ref ReadOnlySequence<byte> buffer, RedisClient client, PipeWriter output) public ValueTask<bool> TryProcessRequestAsync(ref ReadOnlySequence<byte> buffer, RedisClient client, PipeWriter output)
{ {
async ValueTask<bool> Awaited(ValueTask wwrite) async ValueTask<bool> Awaited(ValueTask wwrite, TypedRedisValue rresponse)
{ {
await wwrite; await wwrite;
rresponse.Recycle();
return true; return true;
} }
if (!buffer.IsEmpty && TryParseRequest(ref buffer, out var request)) if (!buffer.IsEmpty && TryParseRequest(ref buffer, out var request))
{ {
RedisResult response; TypedRedisValue response;
try { response = Execute(client, request); } try { response = Execute(client, request); }
finally { request.Recycle(); } finally { request.Recycle(); }
var write = WriteResponseAsync(client, output, response); var write = WriteResponseAsync(client, output, response);
if (!write.IsCompletedSuccessfully) return Awaited(write); if (!write.IsCompletedSuccessfully) return Awaited(write, response);
response.Recycle();
return new ValueTask<bool>(true); return new ValueTask<bool>(true);
} }
return new ValueTask<bool>(false); return new ValueTask<bool>(false);
...@@ -461,13 +462,13 @@ async ValueTask<bool> Awaited(ValueTask wwrite) ...@@ -461,13 +462,13 @@ async ValueTask<bool> Awaited(ValueTask wwrite)
public long TotalCommandsProcesed => _totalCommandsProcesed; public long TotalCommandsProcesed => _totalCommandsProcesed;
public long TotalErrorCount => _totalErrorCount; public long TotalErrorCount => _totalErrorCount;
public RedisResult Execute(RedisClient client, RedisRequest request) public TypedRedisValue Execute(RedisClient client, RedisRequest request)
{ {
if (string.IsNullOrWhiteSpace(request.Command)) return null; // not a request if (string.IsNullOrWhiteSpace(request.Command)) return default; // not a request
Interlocked.Increment(ref _totalCommandsProcesed); Interlocked.Increment(ref _totalCommandsProcesed);
try try
{ {
RedisResult result; TypedRedisValue result;
if (_commands.TryGetValue(request.Command, out var cmd)) if (_commands.TryGetValue(request.Command, out var cmd))
{ {
request = request.AsCommand(cmd.Command); // fixup casing request = request.AsCommand(cmd.Command); // fixup casing
...@@ -490,12 +491,16 @@ public RedisResult Execute(RedisClient client, RedisRequest request) ...@@ -490,12 +491,16 @@ public RedisResult Execute(RedisClient client, RedisRequest request)
} }
else else
{ {
result = null; result = TypedRedisValue.Nil;
} }
if (result == null) Log($"missing command: '{request.Command}'"); if (result.IsNil)
else if (result.Type == ResultType.Error) Interlocked.Increment(ref _totalErrorCount); {
return result ?? CommandNotFound(request.Command); Log($"missing command: '{request.Command}'");
return CommandNotFound(request.Command);
}
if (result.Type == ResultType.Error) Interlocked.Increment(ref _totalErrorCount);
return result;
} }
catch (NotSupportedException) catch (NotSupportedException)
{ {
...@@ -509,12 +514,12 @@ public RedisResult Execute(RedisClient client, RedisRequest request) ...@@ -509,12 +514,12 @@ public RedisResult Execute(RedisClient client, RedisRequest request)
} }
catch (InvalidCastException) catch (InvalidCastException)
{ {
return RedisResult.Create("WRONGTYPE Operation against a key holding the wrong kind of value", ResultType.Error); return TypedRedisValue.Error("WRONGTYPE Operation against a key holding the wrong kind of value");
} }
catch (Exception ex) catch (Exception ex)
{ {
if (!_isShutdown) Log(ex.Message); if (!_isShutdown) Log(ex.Message);
return RedisResult.Create("ERR " + ex.Message, ResultType.Error); return TypedRedisValue.Error("ERR " + ex.Message);
} }
} }
...@@ -525,39 +530,40 @@ internal static string ToLower(RawResult value) ...@@ -525,39 +530,40 @@ internal static string ToLower(RawResult value)
return val.ToLowerInvariant(); return val.ToLowerInvariant();
} }
protected static RedisResult CommandNotFound(string command) protected static TypedRedisValue CommandNotFound(string command)
=> RedisResult.Create($"ERR unknown command '{command}'", ResultType.Error); => TypedRedisValue.Error($"ERR unknown command '{command}'");
[RedisCommand(1, LockFree = true)] [RedisCommand(1, LockFree = true)]
protected virtual RedisResult Command(RedisClient client, RedisRequest request) protected virtual TypedRedisValue Command(RedisClient client, RedisRequest request)
{ {
var results = new RedisResult[_commands.Count]; var results = TypedRedisValue.Rent(_commands.Count);
int index = 0; int index = 0;
foreach (var pair in _commands) foreach (var pair in _commands)
results[index++] = CommandInfo(pair.Value); results[index++] = CommandInfo(pair.Value);
return RedisResult.Create(results); return results;
} }
[RedisCommand(-2, "command", "info", LockFree = true)] [RedisCommand(-2, "command", "info", LockFree = true)]
protected virtual RedisResult CommandInfo(RedisClient client, RedisRequest request) protected virtual TypedRedisValue CommandInfo(RedisClient client, RedisRequest request)
{ {
var results = new RedisResult[request.Count - 2]; var results = TypedRedisValue.Rent(request.Count - 2);
for (int i = 2; i < request.Count; i++) for (int i = 2; i < request.Count; i++)
{ {
results[i - 2] = _commands.TryGetValue(request.GetString(i), out var cmd) results[i - 2] = _commands.TryGetValue(request.GetString(i), out var cmd)
? CommandInfo(cmd) : null; ? CommandInfo(cmd) : TypedRedisValue.NullArray;
} }
return RedisResult.Create(results); return results;
}
private TypedRedisValue CommandInfo(RespCommand command)
{
var arr = TypedRedisValue.Rent(6);
arr[0] = TypedRedisValue.BulkString(command.Command);
arr[1] = TypedRedisValue.Integer(command.NetArity());
arr[2] = TypedRedisValue.EmptyArray;
arr[3] = TypedRedisValue.Zero;
arr[4] = TypedRedisValue.Zero;
arr[5] = TypedRedisValue.Zero;
return arr;
} }
private RedisResult CommandInfo(RespCommand command)
=> RedisResult.Create(new[]
{
RedisResult.Create(command.Command, ResultType.BulkString),
RedisResult.Create(command.NetArity(), ResultType.Integer),
RedisResult.EmptyArray,
RedisResult.Zero,
RedisResult.Zero,
RedisResult.Zero,
});
} }
} }
using System;
using System.Buffers;
namespace StackExchange.Redis
{
/// <summary>
/// A RedisValue with an eplicit encoding type, which could represent an array of items
/// </summary>
public readonly struct TypedRedisValue
{
internal static TypedRedisValue Rent(int count)
=> new TypedRedisValue(ArrayPool<TypedRedisValue>.Shared.Rent(count), count);
/// <summary>
/// An invalid empty value that has no type
/// </summary>
public static TypedRedisValue Nil => default;
/// <summary>
/// Returns whether this value is an invalid empty value
/// </summary>
public bool IsNil => Type == ResultType.None;
/// <summary>
/// Returns whether this value represents a null array
/// </summary>
public bool IsNullArray => Type == ResultType.MultiBulk && _oversizedItems == null;
/// <summary>
/// The type of value being represented
/// </summary>
public ResultType Type { get; }
private readonly RedisValue _value;
private readonly TypedRedisValue[] _oversizedItems;
/// <summary>
/// Gets items from an array by index
/// </summary>
public TypedRedisValue this[int index]
{
get => _oversizedItems[index];
internal set => _oversizedItems[index] = value;
}
/// <summary>
/// Gets the length of the value as an array
/// </summary>
public int Length { get; }
/// <summary>
/// Initialize a TypedRedisValue from a value and optionally a type
/// </summary>
private TypedRedisValue(RedisValue value, ResultType? type = null)
{
Type = type ?? (value.IsInteger ? ResultType.Integer : ResultType.BulkString);
_value = value;
Length = default;
_oversizedItems = default;
}
/// <summary>
/// Initialize a TypedRedisValue that represents an error
/// </summary>
public static TypedRedisValue Error(string value)
=> new TypedRedisValue(value, ResultType.Error);
/// <summary>
/// Initialize a TypedRedisValue that represents a simple string
/// </summary>
public static TypedRedisValue SimpleString(string value)
=> new TypedRedisValue(value, ResultType.SimpleString);
/// <summary>
/// The simple string OK
/// </summary>
public static TypedRedisValue OK { get; } = SimpleString("OK");
internal static TypedRedisValue Zero { get; } = Integer(0);
internal static TypedRedisValue One { get; } = Integer(1);
internal static TypedRedisValue NullArray { get; } = MultiBulk(null);
internal static TypedRedisValue EmptyArray { get; } = MultiBulk(Array.Empty<TypedRedisValue>());
/// <summary>
/// Gets the array elements as a span
/// </summary>
public ReadOnlySpan<TypedRedisValue> Span => new ReadOnlySpan<TypedRedisValue>(_oversizedItems, 0, Length);
internal Span<TypedRedisValue> MutableSpan => new Span<TypedRedisValue>(_oversizedItems, 0, Length);
/// <summary>
/// Initialize a TypedRedisValue that represents an integer
/// </summary>
public static TypedRedisValue Integer(long value)
=> new TypedRedisValue(value, ResultType.Integer);
/// <summary>
/// Initialize a TypedRedisValue from an array
/// </summary>
public static TypedRedisValue MultiBulk(TypedRedisValue[] items)
=> new TypedRedisValue(items, items == null ? 0 : items.Length);
/// <summary>
/// Initialize a TypedRedisValue from an oversized array
/// </summary>
public static TypedRedisValue MultiBulk(TypedRedisValue[] oversizedItems, int count)
=> new TypedRedisValue(oversizedItems, count);
/// <summary>
/// Initialize a TypedRedisValue that represents a bulk string
/// </summary>
public static TypedRedisValue BulkString(RedisValue value)
=> new TypedRedisValue(value, ResultType.BulkString);
private TypedRedisValue(TypedRedisValue[] oversizedItems, int count)
{
if (oversizedItems == null)
{
if (count != 0) throw new ArgumentOutOfRangeException(nameof(count));
}
else
{
if (count < 0 || count > oversizedItems.Length) throw new ArgumentOutOfRangeException(nameof(count));
if (count == 0) oversizedItems = Array.Empty<TypedRedisValue>();
}
_value = default;
Type = ResultType.MultiBulk;
Length = count;
_oversizedItems = oversizedItems;
}
internal void Recycle(int limit = -1)
{
var arr = _oversizedItems;
if (arr != null)
{
if (limit < 0) limit = Length;
for (int i = 0; i < limit; i++)
{
arr[i].Recycle();
}
ArrayPool<TypedRedisValue>.Shared.Return(arr, clearArray: false);
}
}
/// <summary>
/// Get the underlying value assuming that it is a valid type with a meaningful value
/// </summary>
internal RedisValue AsRedisValue() => _value;
/// <summary>
/// Obtain the value as a string
/// </summary>
public override string ToString()
{
switch (Type)
{
case ResultType.BulkString:
case ResultType.SimpleString:
case ResultType.Integer:
case ResultType.Error:
return $"{Type}:{_value}";
case ResultType.MultiBulk:
return $"{Type}:[{Length}]";
default:
return Type.ToString();
}
}
/// <summary>
/// Not supported
/// </summary>
public override int GetHashCode() => throw new NotSupportedException();
/// <summary>
/// Not supported
/// </summary>
public override bool Equals(object obj) => throw new NotSupportedException();
}
}
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