Commit 4f182469 authored by Marc Gravell's avatar Marc Gravell

intermediate commit

parent 15319500
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" /> <PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" />
<PackageReference Include="System.IO.Pipelines" Version="$(CoreFxVersion)" /> <PackageReference Include="System.IO.Pipelines" Version="$(CoreFxVersion)" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="$(CoreFxVersion)" /> <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="$(CoreFxVersion)" />
......
using System; using System;
using System.Globalization; using System.Globalization;
using System.Net; using System.Net;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
...@@ -137,6 +140,50 @@ internal static bool TryParseDouble(string s, out double value) ...@@ -137,6 +140,50 @@ internal static bool TryParseDouble(string s, out double value)
return double.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out value); return double.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out value);
} }
internal static bool TryParseDouble(ReadOnlySpan<byte> s, out double value)
{
if (s.IsEmpty)
{
value = 0;
return false;
}
if (s.Length == 1 && s[0] >= '0' && s[0] <= '9')
{
value = (int)(s[0] - '0');
return true;
}
// need to handle these
if (CaseInsensitiveASCIIEqual("+inf", s) || CaseInsensitiveASCIIEqual("inf", s))
{
value = double.PositiveInfinity;
return true;
}
if (CaseInsensitiveASCIIEqual("-inf", s))
{
value = double.NegativeInfinity;
return true;
}
var ss = DecodeUtf8(s);
return double.TryParse(ss, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out value);
}
internal static unsafe string DecodeUtf8(ReadOnlySpan<byte> s)
{
if (s.IsEmpty) return "";
fixed(byte* ptr = &MemoryMarshal.GetReference(s))
{
return Encoding.UTF8.GetString(ptr, s.Length);
}
}
static bool CaseInsensitiveASCIIEqual(string xLowerCase, ReadOnlySpan<byte> y)
{
if (y.Length != xLowerCase.Length) return false;
for(int i = 0; i < y.Length; i++)
{
if (char.ToLower((char)y[i]) != xLowerCase[i]) return false;
}
return true;
}
internal static EndPoint TryParseEndPoint(string endpoint) internal static EndPoint TryParseEndPoint(string endpoint)
{ {
if (string.IsNullOrWhiteSpace(endpoint)) return null; if (string.IsNullOrWhiteSpace(endpoint)) return null;
...@@ -159,5 +206,31 @@ internal static EndPoint TryParseEndPoint(string endpoint) ...@@ -159,5 +206,31 @@ internal static EndPoint TryParseEndPoint(string endpoint)
return ParseEndPoint(host, port); return ParseEndPoint(host, port);
} }
static readonly Vector<ushort> NonAsciiMask = new Vector<ushort>(0xFF80);
internal static unsafe int GetEncodedLength(string value)
{
if (value.Length == 0) return 0;
int offset = 0;
if (Vector.IsHardwareAccelerated && value.Length >= Vector<ushort>.Count)
{
var vecSpan = MemoryMarshal.Cast<char, Vector<ushort>>(value.AsSpan());
var nonAscii = NonAsciiMask;
int i;
for (i = 0; i < vecSpan.Length; i++)
{
if ((vecSpan[i] & nonAscii) != Vector<ushort>.Zero) break;
}
offset = Vector<ushort>.Count * i;
}
int remaining = value.Length - offset;
if (remaining == 0) return offset; // all ASCII (nice round length, and Vector support)
// handles a) no Vector support, b) anything from the fisrt non-ASCII chunk, c) tail end
fixed (char* ptr = value)
{
return offset + Encoding.UTF8.GetByteCount(ptr + offset, remaining);
}
}
} }
} }
...@@ -697,7 +697,7 @@ private void WriteUnified(PipeWriter writer, byte[] prefix, string value) ...@@ -697,7 +697,7 @@ private void WriteUnified(PipeWriter writer, byte[] prefix, string value)
{ {
// ${total-len}\r\n 3 + MaxInt32TextLen // ${total-len}\r\n 3 + MaxInt32TextLen
// {prefix}{value}\r\n // {prefix}{value}\r\n
int encodedLength = Encoding.UTF8.GetByteCount(value), int encodedLength = Format.GetEncodedLength(value),
prefixLength = prefix == null ? 0 : prefix.Length, prefixLength = prefix == null ? 0 : prefix.Length,
totalLength = prefixLength + encodedLength; totalLength = prefixLength + encodedLength;
......
...@@ -255,7 +255,7 @@ internal static byte[] ConcatenateBytes(byte[] a, object b, byte[] c) ...@@ -255,7 +255,7 @@ internal static byte[] ConcatenateBytes(byte[] a, object b, byte[] c)
int aLen = a?.Length ?? 0, int aLen = a?.Length ?? 0,
bLen = b == null ? 0 : (b is string bLen = b == null ? 0 : (b is string
? Encoding.UTF8.GetByteCount((string)b) ? Format.GetEncodedLength((string)b)
: ((byte[])b).Length), : ((byte[])b).Length),
cLen = c?.Length ?? 0; cLen = c?.Length ?? 0;
......
using System; using System;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
namespace StackExchange.Redis namespace StackExchange.Redis
...@@ -8,43 +9,57 @@ namespace StackExchange.Redis ...@@ -8,43 +9,57 @@ namespace StackExchange.Redis
/// </summary> /// </summary>
public struct RedisValue : IEquatable<RedisValue>, IComparable<RedisValue>, IComparable, IConvertible public struct RedisValue : IEquatable<RedisValue>, IComparable<RedisValue>, IComparable, IConvertible
{ {
internal static readonly RedisValue[] EmptyArray = new RedisValue[0]; internal static readonly RedisValue[] EmptyArray = Array.Empty<RedisValue>();
private static readonly byte[] EmptyByteArr = new byte[0];
private static readonly byte[] IntegerSentinel = new byte[0]; private readonly object _objectOrSentinel;
private readonly byte[] valueBlob; private readonly ReadOnlyMemory<byte> _memory;
private readonly long valueInt64; private readonly long _overlappedValue64;
// internal bool IsNullOrDefaultValue { get { return (valueBlob == null && valueInt64 == 0L) || ((object)valueBlob == (object)NullSentinel); } } // internal bool IsNullOrDefaultValue { get { return (valueBlob == null && valueInt64 == 0L) || ((object)valueBlob == (object)NullSentinel); } }
private RedisValue(long valueInt64, byte[] valueBlob) private RedisValue(long overlappedValue64, ReadOnlyMemory<byte> memory, object objectOrSentinel)
{ {
this.valueInt64 = valueInt64; _overlappedValue64 = overlappedValue64;
this.valueBlob = valueBlob; _memory = memory;
_objectOrSentinel = objectOrSentinel;
} }
private readonly static object Sentinel_Integer = new object();
private readonly static object Sentinel_Raw = new object();
private readonly static object Sentinel_Double = new object();
/// <summary> /// <summary>
/// Represents the string <c>""</c> /// Represents the string <c>""</c>
/// </summary> /// </summary>
public static RedisValue EmptyString { get; } = new RedisValue(0, EmptyByteArr); public static RedisValue EmptyString { get; } = new RedisValue(0, default, Sentinel_Raw);
/// <summary> /// <summary>
/// A null value /// A null value
/// </summary> /// </summary>
public static RedisValue Null { get; } = new RedisValue(0, null); public static RedisValue Null { get; } = new RedisValue(0, default, null);
/// <summary> /// <summary>
/// Indicates whether the value is a primitive integer /// Indicates whether the value is a primitive integer
/// </summary> /// </summary>
public bool IsInteger => valueBlob == IntegerSentinel; public bool IsInteger => _objectOrSentinel == Sentinel_Integer;
/// <summary> /// <summary>
/// Indicates whether the value should be considered a null value /// Indicates whether the value should be considered a null value
/// </summary> /// </summary>
public bool IsNull => valueBlob == null; public bool IsNull => _objectOrSentinel == null;
/// <summary> /// <summary>
/// Indicates whether the value is either null or a zero-length value /// Indicates whether the value is either null or a zero-length value
/// </summary> /// </summary>
public bool IsNullOrEmpty => valueBlob == null || (valueBlob.Length == 0 && !(valueBlob == IntegerSentinel)); public bool IsNullOrEmpty
{
get
{
if (IsNull) return true;
if (_objectOrSentinel == Sentinel_Raw && _memory.Length == 0) return true;
if (_objectOrSentinel is string s && s.Length == 0) return true;
if (_objectOrSentinel is byte[] arr && arr.Length == 0) return true;
return false;
}
}
/// <summary> /// <summary>
/// Indicates whether the value is greater than zero-length or has an integer value /// Indicates whether the value is greater than zero-length or has an integer value
...@@ -58,6 +73,8 @@ private RedisValue(long valueInt64, byte[] valueBlob) ...@@ -58,6 +73,8 @@ private RedisValue(long valueInt64, byte[] valueBlob)
/// <param name="y">The second <see cref="RedisValue"/> to compare.</param> /// <param name="y">The second <see cref="RedisValue"/> to compare.</param>
public static bool operator !=(RedisValue x, RedisValue y) => !(x == y); public static bool operator !=(RedisValue x, RedisValue y) => !(x == y);
private double OverlappedValueDouble => BitConverter.Int64BitsToDouble(_overlappedValue64);
/// <summary> /// <summary>
/// Indicates whether two RedisValue values are equivalent /// Indicates whether two RedisValue values are equivalent
/// </summary> /// </summary>
...@@ -65,25 +82,28 @@ private RedisValue(long valueInt64, byte[] valueBlob) ...@@ -65,25 +82,28 @@ private RedisValue(long valueInt64, byte[] valueBlob)
/// <param name="y">The second <see cref="RedisValue"/> to compare.</param> /// <param name="y">The second <see cref="RedisValue"/> to compare.</param>
public static bool operator ==(RedisValue x, RedisValue y) public static bool operator ==(RedisValue x, RedisValue y)
{ {
if (x.valueBlob == null) return y.valueBlob == null; CompareType xType = x.Type, yType = y.Type;
if (xType == CompareType.Null) return yType == CompareType.Null;
if (xType == CompareType.Null) return false;
if (x.valueBlob == IntegerSentinel) if (xType == yType)
{ {
if (y.valueBlob == IntegerSentinel) switch (xType)
{ {
return x.valueInt64 == y.valueInt64; case CompareType.Double:
return x.OverlappedValueDouble == y.OverlappedValueDouble;
case CompareType.Int64:
return x._overlappedValue64 == y._overlappedValue64;
case CompareType.String:
return (string)x._objectOrSentinel == (string)y._objectOrSentinel;
case CompareType.Raw:
return x._memory.Span.SequenceEqual(y._memory.Span);
} }
else
{
return Equals((byte[])x, (byte[])y);
}
}
else if (y.valueBlob == IntegerSentinel)
{
return Equals((byte[])x, (byte[])y);
} }
return Equals(x.valueBlob, y.valueBlob); // otherwise, compare as strings
return (string)x == (string)y;
} }
/// <summary> /// <summary>
...@@ -92,29 +112,11 @@ private RedisValue(long valueInt64, byte[] valueBlob) ...@@ -92,29 +112,11 @@ private RedisValue(long valueInt64, byte[] valueBlob)
/// <param name="obj">The other <see cref="RedisValue"/> to compare.</param> /// <param name="obj">The other <see cref="RedisValue"/> to compare.</param>
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if (obj == null) return valueBlob == null; if (obj == null) return IsNull;
if (obj is RedisValue) var other = TryParse(obj);
{ if (other.IsNull) return false; // parse fail
return Equals((RedisValue)obj); return this == other;
}
if (obj is string)
{
return (string)obj == (string)this;
}
if (obj is byte[])
{
return Equals((byte[])obj, (byte[])this);
}
if (obj is long)
{
return (long)obj == (long)this;
}
if (obj is int)
{
return (int)obj == (int)this;
}
return false;
} }
/// <summary> /// <summary>
...@@ -128,9 +130,20 @@ public override bool Equals(object obj) ...@@ -128,9 +130,20 @@ public override bool Equals(object obj)
/// </summary> /// </summary>
public override int GetHashCode() public override int GetHashCode()
{ {
if (valueBlob == IntegerSentinel) return valueInt64.GetHashCode(); switch (Type)
if (valueBlob == null) return -1; {
return GetHashCode(valueBlob); case CompareType.Null:
return -1;
case CompareType.Double:
return OverlappedValueDouble.GetHashCode();
case CompareType.Int64:
return _overlappedValue64.GetHashCode();
case CompareType.Raw:
return GetHashCode(_memory);
case CompareType.String:
default:
return _objectOrSentinel.GetHashCode();
}
} }
/// <summary> /// <summary>
...@@ -162,35 +175,31 @@ internal static unsafe bool Equals(byte[] x, byte[] y) ...@@ -162,35 +175,31 @@ internal static unsafe bool Equals(byte[] x, byte[] y)
return true; return true;
} }
internal static unsafe int GetHashCode(byte[] value) internal static unsafe int GetHashCode(ReadOnlyMemory<byte> memory)
{ {
unchecked unchecked
{ {
if (value == null) return -1; var span8 = memory.Span;
int len = value.Length; int len = span8.Length;
if (len == 0) return 0; if (len == 0) return 0;
int octets = len / 8, spare = len % 8;
int acc = 728271210; int acc = 728271210;
fixed (byte* ptr8 = value)
{ var span64 = MemoryMarshal.Cast<byte, long>(span8);
long* ptr64 = (long*)ptr8; for (int i = 0; i < span64.Length; i++)
for (int i = 0; i < octets; i++)
{ {
long val = ptr64[i]; var val = span64[i];
int valHash = (((int)val) ^ ((int)(val >> 32))); int valHash = (((int)val) ^ ((int)(val >> 32)));
acc = (((acc << 5) + acc) ^ valHash); acc = (((acc << 5) + acc) ^ valHash);
} }
int offset = len - spare; int spare = len % 8, offset = len - spare;
while (spare-- != 0) while (spare-- != 0)
{ {
acc = (((acc << 5) + acc) ^ ptr8[offset++]); acc = (((acc << 5) + acc) ^ span8[offset++]);
}
} }
return acc; return acc;
} }
} }
internal static bool TryParseInt64(byte[] value, int offset, int count, out long result)
=> TryParseInt64(new ReadOnlySpan<byte>(value, offset, count), out result);
internal static bool TryParseInt64(ReadOnlySpan<byte> value, out long result) internal static bool TryParseInt64(ReadOnlySpan<byte> value, out long result)
{ {
result = 0; result = 0;
...@@ -220,6 +229,35 @@ internal static bool TryParseInt64(ReadOnlySpan<byte> value, out long result) ...@@ -220,6 +229,35 @@ internal static bool TryParseInt64(ReadOnlySpan<byte> value, out long result)
} }
} }
} }
internal static bool TryParseInt64(string value, out long result)
{
result = 0;
if (string.IsNullOrEmpty(value)) return false;
checked
{
int max = value.Length;
if (value[0] == '-')
{
for (int i = 1; i < max; i++)
{
var b = value[i];
if (b < '0' || b > '9') return false;
result = (result * 10) - (b - '0');
}
return true;
}
else
{
for (int i = 0; i < max; i++)
{
var b = value[i];
if (b < '0' || b > '9') return false;
result = (result * 10) + (b - '0');
}
return true;
}
}
}
internal void AssertNotNull() internal void AssertNotNull()
{ {
...@@ -228,37 +266,22 @@ internal void AssertNotNull() ...@@ -228,37 +266,22 @@ internal void AssertNotNull()
private enum CompareType private enum CompareType
{ {
Null, Int64, Double, Raw Null, Int64, Double, Raw, String,
} }
private CompareType ResolveType(out long i64, out double r8) private CompareType Type
{
byte[] blob = valueBlob;
if (blob == IntegerSentinel)
{ {
i64 = valueInt64; get
r8 = default(double);
return CompareType.Int64;
}
if (blob == null)
{ {
i64 = default(long); var objectOrSentinel = _objectOrSentinel;
r8 = default(double); if (objectOrSentinel == null) return CompareType.Null;
return CompareType.Null; if (objectOrSentinel == Sentinel_Integer) return CompareType.Int64;
if (objectOrSentinel == Sentinel_Double) return CompareType.Double;
if (objectOrSentinel == Sentinel_Raw) return CompareType.Raw;
if (objectOrSentinel is string) return CompareType.String;
if (objectOrSentinel is byte[]) return CompareType.Raw; // doubled-up, but retaining the array
throw new InvalidOperationException("Unknown type");
} }
if (TryParseInt64(blob, 0, blob.Length, out i64))
{
r8 = default(double);
return CompareType.Int64;
}
if (TryParseDouble(blob, out r8))
{
i64 = default(long);
return CompareType.Double;
}
i64 = default(long);
r8 = default(double);
return CompareType.Raw;
} }
/// <summary> /// <summary>
...@@ -269,30 +292,29 @@ public int CompareTo(RedisValue other) ...@@ -269,30 +292,29 @@ public int CompareTo(RedisValue other)
{ {
try try
{ {
CompareType thisType = ResolveType(out long thisInt64, out double thisDouble), CompareType thisType = this.Type,
otherType = other.ResolveType(out long otherInt64, out double otherDouble); otherType = other.Type;
if (thisType == CompareType.Null) if (thisType == CompareType.Null) return otherType == CompareType.Null ? 0 : -1;
{ if (otherType == CompareType.Null) return 1;
return otherType == CompareType.Null ? 0 : -1;
}
if (otherType == CompareType.Null)
{
return 1;
}
if (thisType == CompareType.Int64) if (thisType == otherType)
{ {
if (otherType == CompareType.Int64) return thisInt64.CompareTo(otherInt64); switch (thisType)
if (otherType == CompareType.Double) return ((double)thisInt64).CompareTo(otherDouble);
}
else if (thisType == CompareType.Double)
{ {
if (otherType == CompareType.Int64) return thisDouble.CompareTo((double)otherInt64); case CompareType.Double:
if (otherType == CompareType.Double) return thisDouble.CompareTo(otherDouble); return this.OverlappedValueDouble.CompareTo(other.OverlappedValueDouble);
case CompareType.Int64:
return this._overlappedValue64.CompareTo(other._overlappedValue64);
case CompareType.String:
return string.CompareOrdinal((string)this._objectOrSentinel, (string)other._objectOrSentinel);
case CompareType.Raw:
return this._memory.Span.SequenceCompareTo(other._memory.Span);
} }
}
// otherwise, compare as strings // otherwise, compare as strings
return StringComparer.InvariantCulture.Compare((string)this, (string)other); return string.CompareOrdinal((string)this, (string)other);
} }
catch (Exception ex) catch (Exception ex)
{ {
...@@ -304,20 +326,36 @@ public int CompareTo(RedisValue other) ...@@ -304,20 +326,36 @@ public int CompareTo(RedisValue other)
int IComparable.CompareTo(object obj) int IComparable.CompareTo(object obj)
{ {
if (obj is RedisValue) return CompareTo((RedisValue)obj); if (obj == null) return CompareTo(Null);
if (obj is long) return CompareTo((RedisValue)(long)obj);
if (obj is double) return CompareTo((RedisValue)(double)obj); var val = TryParse(obj);
if (obj is string) return CompareTo((RedisValue)(string)obj); if (val.IsNull) return -1; // parse fail
if (obj is byte[]) return CompareTo((RedisValue)(byte[])obj);
if (obj is bool) return CompareTo((RedisValue)(bool)obj); return CompareTo(val);
return -1; }
internal static RedisValue TryParse(object obj)
{
if (obj == null) return RedisValue.Null;
if (obj is RedisValue) return (RedisValue)obj;
if (obj is string) return (RedisValue)(string)obj;
if (obj is int) return (RedisValue)(int)obj;
if (obj is double) return (RedisValue)(double)obj;
if (obj is byte[]) return (RedisValue)(byte[])obj;
if (obj is bool) return (RedisValue)(bool)obj;
if (obj is long) return (RedisValue)(long)obj;
if (obj is float) return (RedisValue)(float)obj;
if (obj is ReadOnlyMemory<byte>) return (RedisValue)(ReadOnlyMemory<byte>)obj;
if (obj is Memory<byte>) return (RedisValue)(Memory<byte>)obj;
return Null;
} }
/// <summary> /// <summary>
/// Creates a new <see cref="RedisValue"/> from an <see cref="int"/>. /// Creates a new <see cref="RedisValue"/> from an <see cref="int"/>.
/// </summary> /// </summary>
/// <param name="value">The <see cref="int"/> to convert to a <see cref="RedisValue"/>.</param> /// <param name="value">The <see cref="int"/> to convert to a <see cref="RedisValue"/>.</param>
public static implicit operator RedisValue(int value) => new RedisValue(value, IntegerSentinel); public static implicit operator RedisValue(int value) => new RedisValue(value, default, Sentinel_Integer);
/// <summary> /// <summary>
/// Creates a new <see cref="RedisValue"/> from an <see cref="T:Nullable{int}"/>. /// Creates a new <see cref="RedisValue"/> from an <see cref="T:Nullable{int}"/>.
...@@ -329,7 +367,7 @@ int IComparable.CompareTo(object obj) ...@@ -329,7 +367,7 @@ int IComparable.CompareTo(object obj)
/// Creates a new <see cref="RedisValue"/> from an <see cref="long"/>. /// Creates a new <see cref="RedisValue"/> from an <see cref="long"/>.
/// </summary> /// </summary>
/// <param name="value">The <see cref="long"/> to convert to a <see cref="RedisValue"/>.</param> /// <param name="value">The <see cref="long"/> to convert to a <see cref="RedisValue"/>.</param>
public static implicit operator RedisValue(long value) => new RedisValue(value, IntegerSentinel); public static implicit operator RedisValue(long value) => new RedisValue(value, default, Sentinel_Integer);
/// <summary> /// <summary>
/// Creates a new <see cref="RedisValue"/> from an <see cref="T:Nullable{long}"/>. /// Creates a new <see cref="RedisValue"/> from an <see cref="T:Nullable{long}"/>.
...@@ -341,7 +379,16 @@ int IComparable.CompareTo(object obj) ...@@ -341,7 +379,16 @@ int IComparable.CompareTo(object obj)
/// Creates a new <see cref="RedisValue"/> from an <see cref="double"/>. /// Creates a new <see cref="RedisValue"/> from an <see cref="double"/>.
/// </summary> /// </summary>
/// <param name="value">The <see cref="double"/> to convert to a <see cref="RedisValue"/>.</param> /// <param name="value">The <see cref="double"/> to convert to a <see cref="RedisValue"/>.</param>
public static implicit operator RedisValue(double value) => Format.ToString(value); public static implicit operator RedisValue(double value)
{
try
{
var i64 = (long)value;
if (value == i64) return new RedisValue(i64, default, Sentinel_Integer);
}
catch { }
return new RedisValue(BitConverter.DoubleToInt64Bits(value), default, Sentinel_Double);
}
/// <summary> /// <summary>
/// Creates a new <see cref="RedisValue"/> from an <see cref="T:Nullable{double}"/>. /// Creates a new <see cref="RedisValue"/> from an <see cref="T:Nullable{double}"/>.
...@@ -349,52 +396,48 @@ int IComparable.CompareTo(object obj) ...@@ -349,52 +396,48 @@ int IComparable.CompareTo(object obj)
/// <param name="value">The <see cref="T:Nullable{double}"/> to convert to a <see cref="RedisValue"/>.</param> /// <param name="value">The <see cref="T:Nullable{double}"/> to convert to a <see cref="RedisValue"/>.</param>
public static implicit operator RedisValue(double? value) => value == null ? Null : (RedisValue)value.GetValueOrDefault(); public static implicit operator RedisValue(double? value) => value == null ? Null : (RedisValue)value.GetValueOrDefault();
/// <summary>
/// Creates a new <see cref="RedisValue"/> from a <see cref="T:ReadOnlyMemory{byte}"/>.
/// </summary>
public static implicit operator RedisValue(ReadOnlyMemory<byte> value)
{
if (value.Length == 0) return EmptyString;
return new RedisValue(0, value, Sentinel_Raw);
}
/// <summary>
/// Creates a new <see cref="RedisValue"/> from a <see cref="T:Memory{byte}"/>.
/// </summary>
public static implicit operator RedisValue(Memory<byte> value) => (ReadOnlyMemory<byte>)value;
/// <summary> /// <summary>
/// Creates a new <see cref="RedisValue"/> from an <see cref="string"/>. /// Creates a new <see cref="RedisValue"/> from an <see cref="string"/>.
/// </summary> /// </summary>
/// <param name="value">The <see cref="string"/> to convert to a <see cref="RedisValue"/>.</param> /// <param name="value">The <see cref="string"/> to convert to a <see cref="RedisValue"/>.</param>
public static implicit operator RedisValue(string value) public static implicit operator RedisValue(string value)
{ {
byte[] blob; if (value == null) return Null;
if (value == null) blob = null; if (value.Length == 0) return EmptyString;
else if (value.Length == 0) blob = EmptyByteArr; return new RedisValue(0, default, value);
else blob = Encoding.UTF8.GetBytes(value);
return new RedisValue(0, blob);
} }
/// <summary> /// <summary>
/// Creates a new <see cref="RedisValue"/> from an <see cref="T:byte[]"/>. /// Creates a new <see cref="RedisValue"/> from an <see cref="T:byte[]"/>.
/// </summary> /// </summary>
/// <param name="value">The <see cref="T:byte[]"/> to convert to a <see cref="RedisValue"/>.</param> /// <param name="value">The <see cref="T:byte[]"/> to convert to a <see cref="RedisValue"/>.</param>
public static implicit operator RedisValue(byte[] value) public static implicit operator RedisValue(byte[] value)
{ {
byte[] blob; if (value == null) return Null;
if (value == null) blob = null; if (value.Length == 0) return EmptyString;
else if (value.Length == 0) blob = EmptyByteArr; return new RedisValue(0, new Memory<byte>(value), value);
else blob = value;
return new RedisValue(0, blob);
}
internal static RedisValue Parse(object obj)
{
if (obj == null) return RedisValue.Null;
if (obj is RedisValue) return (RedisValue)obj;
if (obj is string) return (RedisValue)(string)obj;
if (obj is int) return (RedisValue)(int)obj;
if (obj is double) return (RedisValue)(double)obj;
if (obj is byte[]) return (RedisValue)(byte[])obj;
if (obj is bool) return (RedisValue)(bool)obj;
if (obj is long) return (RedisValue)(long)obj;
if (obj is float) return (RedisValue)(float)obj;
throw new InvalidOperationException("Unable to format type for redis: " + obj.GetType().FullName);
} }
/// <summary> /// <summary>
/// Creates a new <see cref="RedisValue"/> from an <see cref="bool"/>. /// Creates a new <see cref="RedisValue"/> from an <see cref="bool"/>.
/// </summary> /// </summary>
/// <param name="value">The <see cref="bool"/> to convert to a <see cref="RedisValue"/>.</param> /// <param name="value">The <see cref="bool"/> to convert to a <see cref="RedisValue"/>.</param>
public static implicit operator RedisValue(bool value) => new RedisValue(value ? 1 : 0, IntegerSentinel); public static implicit operator RedisValue(bool value) => new RedisValue(value ? 1 : 0, default, Sentinel_Integer);
/// <summary> /// <summary>
/// Creates a new <see cref="RedisValue"/> from an <see cref="T:Nullable{bool}"/>. /// Creates a new <see cref="RedisValue"/> from an <see cref="T:Nullable{bool}"/>.
...@@ -434,10 +477,24 @@ internal static RedisValue Parse(object obj) ...@@ -434,10 +477,24 @@ internal static RedisValue Parse(object obj)
/// <param name="value">The <see cref="RedisValue"/> to convert.</param> /// <param name="value">The <see cref="RedisValue"/> to convert.</param>
public static explicit operator long(RedisValue value) public static explicit operator long(RedisValue value)
{ {
var blob = value.valueBlob; switch (value.Type)
if (blob == IntegerSentinel) return value.valueInt64; {
if (blob == null) return 0; // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr") case CompareType.Null:
if (TryParseInt64(blob, 0, blob.Length, out long i64)) return i64; return 0; // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr")
case CompareType.Int64:
return value._overlappedValue64;
case CompareType.Double:
var f64 = value.OverlappedValueDouble;
var i64 = (long)f64;
if (f64 == i64) return i64;
break;
case CompareType.String:
if (TryParseInt64((string)value._objectOrSentinel, out i64)) return i64;
break;
case CompareType.Raw:
if (TryParseInt64(value._memory.Span, out i64)) return i64;
break;
}
throw new InvalidCastException(); throw new InvalidCastException();
} }
...@@ -447,24 +504,34 @@ internal static RedisValue Parse(object obj) ...@@ -447,24 +504,34 @@ internal static RedisValue Parse(object obj)
/// <param name="value">The <see cref="RedisValue"/> to convert.</param> /// <param name="value">The <see cref="RedisValue"/> to convert.</param>
public static explicit operator double(RedisValue value) public static explicit operator double(RedisValue value)
{ {
var blob = value.valueBlob; switch (value.Type)
if (blob == IntegerSentinel) return value.valueInt64; {
if (blob == null) return 0; // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr") case CompareType.Null:
return 0; // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr")
if (TryParseDouble(blob, out double r8)) return r8; case CompareType.Int64:
return value._overlappedValue64;
case CompareType.Double:
return value.OverlappedValueDouble;
case CompareType.String:
if (TryParseInt64((string)value._objectOrSentinel, out var f64)) return f64;
break;
case CompareType.Raw:
if (TryParseInt64(value._memory.Span, out f64)) return f64;
break;
}
throw new InvalidCastException(); throw new InvalidCastException();
} }
private static bool TryParseDouble(byte[] blob, out double value) private static bool TryParseDouble(ReadOnlySpan<byte> blob, out double value)
{ {
// simple integer? // simple integer?
if (blob.Length == 1 && blob[0] >= '0' && blob[0] <= '9') if (TryParseInt64(blob, out var i64))
{ {
value = blob[0] - '0'; value = i64;
return true; return true;
} }
return Format.TryParseDouble(Encoding.UTF8.GetString(blob), out value); return Format.TryParseDouble(blob, out value);
} }
/// <summary> /// <summary>
...@@ -473,7 +540,7 @@ private static bool TryParseDouble(byte[] blob, out double value) ...@@ -473,7 +540,7 @@ private static bool TryParseDouble(byte[] blob, out double value)
/// <param name="value">The <see cref="RedisValue"/> to convert.</param> /// <param name="value">The <see cref="RedisValue"/> to convert.</param>
public static explicit operator double? (RedisValue value) public static explicit operator double? (RedisValue value)
{ {
if (value.valueBlob == null) return null; if (value.IsNull) return null;
return (double)value; return (double)value;
} }
...@@ -483,7 +550,7 @@ private static bool TryParseDouble(byte[] blob, out double value) ...@@ -483,7 +550,7 @@ private static bool TryParseDouble(byte[] blob, out double value)
/// <param name="value">The <see cref="RedisValue"/> to convert.</param> /// <param name="value">The <see cref="RedisValue"/> to convert.</param>
public static explicit operator long? (RedisValue value) public static explicit operator long? (RedisValue value)
{ {
if (value.valueBlob == null) return null; if (value.IsNull) return null;
return (long)value; return (long)value;
} }
...@@ -493,7 +560,7 @@ private static bool TryParseDouble(byte[] blob, out double value) ...@@ -493,7 +560,7 @@ private static bool TryParseDouble(byte[] blob, out double value)
/// <param name="value">The <see cref="RedisValue"/> to convert.</param> /// <param name="value">The <see cref="RedisValue"/> to convert.</param>
public static explicit operator int? (RedisValue value) public static explicit operator int? (RedisValue value)
{ {
if (value.valueBlob == null) return null; if (value.IsNull) return null;
return (int)value; return (int)value;
} }
...@@ -503,7 +570,7 @@ private static bool TryParseDouble(byte[] blob, out double value) ...@@ -503,7 +570,7 @@ private static bool TryParseDouble(byte[] blob, out double value)
/// <param name="value">The <see cref="RedisValue"/> to convert.</param> /// <param name="value">The <see cref="RedisValue"/> to convert.</param>
public static explicit operator bool? (RedisValue value) public static explicit operator bool? (RedisValue value)
{ {
if (value.valueBlob == null) return null; if (value.IsNull) return null;
return (bool)value; return (bool)value;
} }
...@@ -513,24 +580,51 @@ private static bool TryParseDouble(byte[] blob, out double value) ...@@ -513,24 +580,51 @@ private static bool TryParseDouble(byte[] blob, out double value)
/// <param name="value">The <see cref="RedisValue"/> to convert.</param> /// <param name="value">The <see cref="RedisValue"/> to convert.</param>
public static implicit operator string(RedisValue value) public static implicit operator string(RedisValue value)
{ {
var valueBlob = value.valueBlob; switch (value.Type)
if (valueBlob == IntegerSentinel)
return Format.ToString(value.valueInt64);
if (valueBlob == null) return null;
if (valueBlob.Length == 0) return "";
if (valueBlob.Length == 2 && valueBlob[0] == (byte)'O' && valueBlob[1] == (byte)'K')
{ {
return "OK"; // special case for +OK status results from modules case CompareType.Null: return null;
} case CompareType.Double: return Format.ToString(value.OverlappedValueDouble.ToString());
case CompareType.Int64: return Format.ToString(value._overlappedValue64);
case CompareType.String: return (string)value._objectOrSentinel;
case CompareType.Raw:
var span = value._memory.Span;
if (span.IsEmpty) return "";
if (span.Length == 2 && span[0] == (byte)'O' && span[1] == (byte)'K') return "OK"; // frequent special-case
try try
{ {
return Encoding.UTF8.GetString(valueBlob); return Format.DecodeUtf8(span);
} }
catch catch
{ {
return BitConverter.ToString(valueBlob); return ToHex(span);
}
default:
throw new InvalidOperationException();
}
}
static string ToHex(ReadOnlySpan<byte> src)
{
const string HexValues = "0123456789ABCDEF";
if (src.IsEmpty) return "";
var s = new string((char)0, src.Length * 3 - 1);
var dst = MemoryMarshal.AsMemory(s.AsMemory()).Span;
int i = 0;
int j = 0;
byte b = src[i++];
dst[j++] = HexValues[b >> 4];
dst[j++] = HexValues[b & 0xF];
while (i < src.Length)
{
b = src[i++];
dst[j++] = '-';
dst[j++] = HexValues[b >> 4];
dst[j++] = HexValues[b & 0xF];
} }
return s;
} }
/// <summary> /// <summary>
...@@ -539,12 +633,24 @@ private static bool TryParseDouble(byte[] blob, out double value) ...@@ -539,12 +633,24 @@ private static bool TryParseDouble(byte[] blob, out double value)
/// <param name="value">The <see cref="RedisValue"/> to convert.</param> /// <param name="value">The <see cref="RedisValue"/> to convert.</param>
public static implicit operator byte[] (RedisValue value) public static implicit operator byte[] (RedisValue value)
{ {
var valueBlob = value.valueBlob; if (value.Type == CompareType.Raw)
if (valueBlob == IntegerSentinel) {
return value._objectOrSentinel is byte[] arr ? arr : value._memory.ToArray();
}
return Encoding.UTF8.GetBytes((string)value);
}
/// <summary>
/// Converts a <see cref="RedisValue"/> to a ReadOnlyMemory
/// </summary>
/// <param name="value">The <see cref="RedisValue"/> to convert.</param>
public static implicit operator ReadOnlyMemory<byte> (RedisValue value)
{
if (value.Type == CompareType.Raw)
{ {
return Encoding.UTF8.GetBytes(Format.ToString(value.valueInt64)); return value._memory;
} }
return valueBlob; return Encoding.UTF8.GetBytes((string)value);
} }
TypeCode IConvertible.GetTypeCode() => TypeCode.Object; TypeCode IConvertible.GetTypeCode() => TypeCode.Object;
...@@ -566,8 +672,9 @@ object IConvertible.ToType(Type conversionType, IFormatProvider provider) ...@@ -566,8 +672,9 @@ object IConvertible.ToType(Type conversionType, IFormatProvider provider)
{ {
if (conversionType == null) throw new ArgumentNullException(nameof(conversionType)); if (conversionType == null) throw new ArgumentNullException(nameof(conversionType));
if (conversionType == typeof(byte[])) return (byte[])this; if (conversionType == typeof(byte[])) return (byte[])this;
if (conversionType == typeof(ReadOnlyMemory<byte>)) return (ReadOnlyMemory<byte>)this;
if (conversionType == typeof(RedisValue)) return this; if (conversionType == typeof(RedisValue)) return this;
switch (Type.GetTypeCode(conversionType)) switch (System.Type.GetTypeCode(conversionType))
{ {
case TypeCode.Boolean: return (bool)this; case TypeCode.Boolean: return (bool)this;
case TypeCode.Byte: return (byte)this; case TypeCode.Byte: return (byte)this;
...@@ -602,6 +709,11 @@ object IConvertible.ToType(Type conversionType, IFormatProvider provider) ...@@ -602,6 +709,11 @@ object IConvertible.ToType(Type conversionType, IFormatProvider provider)
/// <param name="val">The <see cref="long"/> value, if conversion was possible.</param> /// <param name="val">The <see cref="long"/> value, if conversion was possible.</param>
public bool TryParse(out long val) public bool TryParse(out long val)
{ {
switch(Type)
{
case CompareType.Int64: val =_overlappedValue64; return true;
case CompareType.String:
}
var blob = valueBlob; var blob = valueBlob;
if (blob == IntegerSentinel) if (blob == IntegerSentinel)
{ {
......
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