Commit fb4a6308 authored by Nick Craver's avatar Nick Craver

Line endings fix: whole files

parent e6a3e65b
// .NET port of https://github.com/RedisLabs/JRediSearch/ // .NET port of https://github.com/RedisLabs/JRediSearch/
using StackExchange.Redis; using StackExchange.Redis;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NRediSearch namespace NRediSearch
{ {
public sealed class Client public sealed class Client
{ {
[Flags] [Flags]
public enum IndexOptions public enum IndexOptions
{ {
/// <summary> /// <summary>
/// All options disabled /// All options disabled
/// </summary> /// </summary>
None = 0, None = 0,
/// <summary> /// <summary>
/// Set this to tell the index not to save term offset vectors. This reduces memory consumption but does not /// Set this to tell the index not to save term offset vectors. This reduces memory consumption but does not
/// allow performing exact matches, and reduces overall relevance of multi-term queries /// allow performing exact matches, and reduces overall relevance of multi-term queries
/// </summary> /// </summary>
UseTermOffsets = 1, UseTermOffsets = 1,
/// <summary> /// <summary>
/// If set (default), we keep flags per index record telling us what fields the term appeared on, /// If set (default), we keep flags per index record telling us what fields the term appeared on,
/// and allowing us to filter results by field /// and allowing us to filter results by field
/// </summary> /// </summary>
KeepFieldFlags = 2, KeepFieldFlags = 2,
/// <summary> /// <summary>
/// The default indexing options - use term offsets and keep fields flags /// The default indexing options - use term offsets and keep fields flags
/// </summary> /// </summary>
Default = UseTermOffsets | KeepFieldFlags, Default = UseTermOffsets | KeepFieldFlags,
/// <summary> /// <summary>
/// If set, we keep an index of the top entries per term, allowing extremely fast single word queries /// If set, we keep an index of the top entries per term, allowing extremely fast single word queries
/// regardless of index size, at the cost of more memory /// regardless of index size, at the cost of more memory
/// </summary> /// </summary>
UseScoreIndexes = 4, UseScoreIndexes = 4,
/// <summary> /// <summary>
/// If set, we will disable the Stop-Words completely /// If set, we will disable the Stop-Words completely
/// </summary> /// </summary>
DisableStopWords = 8 DisableStopWords = 8
} }
private static void SerializeRedisArgs(IndexOptions flags, List<object> args) private static void SerializeRedisArgs(IndexOptions flags, List<object> args)
{ {
if ((flags & IndexOptions.UseTermOffsets) == 0) if ((flags & IndexOptions.UseTermOffsets) == 0)
{ {
args.Add("NOOFFSETS".Literal()); args.Add("NOOFFSETS".Literal());
} }
if ((flags & IndexOptions.KeepFieldFlags) == 0) if ((flags & IndexOptions.KeepFieldFlags) == 0)
{ {
args.Add("NOFIELDS".Literal()); args.Add("NOFIELDS".Literal());
} }
if ((flags & IndexOptions.UseScoreIndexes) == 0) if ((flags & IndexOptions.UseScoreIndexes) == 0)
{ {
args.Add("NOSCOREIDX".Literal()); args.Add("NOSCOREIDX".Literal());
} }
if ((flags & IndexOptions.DisableStopWords) == IndexOptions.DisableStopWords) if ((flags & IndexOptions.DisableStopWords) == IndexOptions.DisableStopWords)
{ {
args.Add("STOPWORDS".Literal()); args.Add("STOPWORDS".Literal());
args.Add(0); args.Add(0);
} }
} }
private readonly IDatabaseAsync _db; private readonly IDatabaseAsync _db;
private IDatabase DbSync private IDatabase DbSync
=> (_db as IDatabase) ?? throw new InvalidOperationException("Synchronous operations are not available on this database instance"); => (_db as IDatabase) ?? throw new InvalidOperationException("Synchronous operations are not available on this database instance");
private readonly object _boxedIndexName; private readonly object _boxedIndexName;
public RedisKey IndexName => (RedisKey)_boxedIndexName; public RedisKey IndexName => (RedisKey)_boxedIndexName;
public Client(RedisKey indexName, IDatabaseAsync db) public Client(RedisKey indexName, IDatabaseAsync db)
{ {
_db = db ?? throw new ArgumentNullException(nameof(db)); _db = db ?? throw new ArgumentNullException(nameof(db));
_boxedIndexName = indexName; // only box once, not per-command _boxedIndexName = indexName; // only box once, not per-command
} }
public Client(RedisKey indexName, IDatabase db) : this(indexName, (IDatabaseAsync)db) { } public Client(RedisKey indexName, IDatabase db) : this(indexName, (IDatabaseAsync)db) { }
/// <summary> /// <summary>
/// Create the index definition in redis /// Create the index definition in redis
/// </summary> /// </summary>
/// <param name="schema">a schema definition <seealso cref="Schema"/></param> /// <param name="schema">a schema definition <seealso cref="Schema"/></param>
/// <param name="options">index option flags <seealso cref="IndexOptions"/></param> /// <param name="options">index option flags <seealso cref="IndexOptions"/></param>
/// <returns>true if successful</returns> /// <returns>true if successful</returns>
public bool CreateIndex(Schema schema, IndexOptions options) public bool CreateIndex(Schema schema, IndexOptions options)
{ {
var args = new List<object> var args = new List<object>
{ {
_boxedIndexName _boxedIndexName
}; };
SerializeRedisArgs(options, args); SerializeRedisArgs(options, args);
args.Add("SCHEMA".Literal()); args.Add("SCHEMA".Literal());
foreach (var f in schema.Fields) foreach (var f in schema.Fields)
{ {
f.SerializeRedisArgs(args); f.SerializeRedisArgs(args);
} }
return (string)DbSync.Execute("FT.CREATE", args) == "OK"; return (string)DbSync.Execute("FT.CREATE", args) == "OK";
} }
/// <summary> /// <summary>
/// Create the index definition in redis /// Create the index definition in redis
/// </summary> /// </summary>
/// <param name="schema">a schema definition <seealso cref="Schema"/></param> /// <param name="schema">a schema definition <seealso cref="Schema"/></param>
/// <param name="options">index option flags <seealso cref="IndexOptions"/></param> /// <param name="options">index option flags <seealso cref="IndexOptions"/></param>
/// <returns>true if successful</returns> /// <returns>true if successful</returns>
public async Task<bool> CreateIndexAsync(Schema schema, IndexOptions options) public async Task<bool> CreateIndexAsync(Schema schema, IndexOptions options)
{ {
var args = new List<object> var args = new List<object>
{ {
_boxedIndexName _boxedIndexName
}; };
SerializeRedisArgs(options, args); SerializeRedisArgs(options, args);
args.Add("SCHEMA".Literal()); args.Add("SCHEMA".Literal());
foreach (var f in schema.Fields) foreach (var f in schema.Fields)
{ {
f.SerializeRedisArgs(args); f.SerializeRedisArgs(args);
} }
return (string)await _db.ExecuteAsync("FT.CREATE", args).ConfigureAwait(false) == "OK"; return (string)await _db.ExecuteAsync("FT.CREATE", args).ConfigureAwait(false) == "OK";
} }
/// <summary> /// <summary>
/// Search the index /// Search the index
/// </summary> /// </summary>
/// <param name="q">a <see cref="Query"/> object with the query string and optional parameters</param> /// <param name="q">a <see cref="Query"/> object with the query string and optional parameters</param>
/// <returns>a <see cref="SearchResult"/> object with the results</returns> /// <returns>a <see cref="SearchResult"/> object with the results</returns>
public SearchResult Search(Query q) public SearchResult Search(Query q)
{ {
var args = new List<object> var args = new List<object>
{ {
_boxedIndexName _boxedIndexName
}; };
q.SerializeRedisArgs(args); q.SerializeRedisArgs(args);
var resp = (RedisResult[])DbSync.Execute("FT.SEARCH", args); var resp = (RedisResult[])DbSync.Execute("FT.SEARCH", args);
return new SearchResult(resp, !q.NoContent, q.WithScores, q.WithPayloads); return new SearchResult(resp, !q.NoContent, q.WithScores, q.WithPayloads);
} }
/// <summary> /// <summary>
/// Search the index /// Search the index
/// </summary> /// </summary>
/// <param name="q">a <see cref="Query"/> object with the query string and optional parameters</param> /// <param name="q">a <see cref="Query"/> object with the query string and optional parameters</param>
/// <returns>a <see cref="SearchResult"/> object with the results</returns> /// <returns>a <see cref="SearchResult"/> object with the results</returns>
public async Task<SearchResult> SearchAsync(Query q) public async Task<SearchResult> SearchAsync(Query q)
{ {
var args = new List<object> var args = new List<object>
{ {
_boxedIndexName _boxedIndexName
}; };
q.SerializeRedisArgs(args); q.SerializeRedisArgs(args);
var resp = (RedisResult[])await _db.ExecuteAsync("FT.SEARCH", args).ConfigureAwait(false); var resp = (RedisResult[])await _db.ExecuteAsync("FT.SEARCH", args).ConfigureAwait(false);
return new SearchResult(resp, !q.NoContent, q.WithScores, q.WithPayloads); return new SearchResult(resp, !q.NoContent, q.WithScores, q.WithPayloads);
} }
/// <summary> /// <summary>
/// Return Distinct Values in a TAG field /// Return Distinct Values in a TAG field
/// </summary> /// </summary>
/// <param name="fieldName">TAG field name</param> /// <param name="fieldName">TAG field name</param>
/// <returns>List of TAG field values</returns> /// <returns>List of TAG field values</returns>
public RedisValue[] TagVals(string fieldName) => public RedisValue[] TagVals(string fieldName) =>
(RedisValue[])DbSync.Execute("FT.TAGVALS", _boxedIndexName, fieldName); (RedisValue[])DbSync.Execute("FT.TAGVALS", _boxedIndexName, fieldName);
/// <summary> /// <summary>
/// Return Distinct Values in a TAG field /// Return Distinct Values in a TAG field
/// </summary> /// </summary>
/// <param name="fieldName">TAG field name</param> /// <param name="fieldName">TAG field name</param>
/// <returns>List of TAG field values</returns> /// <returns>List of TAG field values</returns>
public async Task<RedisValue[]> TagValsAsync(string fieldName) => public async Task<RedisValue[]> TagValsAsync(string fieldName) =>
(RedisValue[])await _db.ExecuteAsync("FT.TAGVALS", _boxedIndexName, fieldName).ConfigureAwait(false); (RedisValue[])await _db.ExecuteAsync("FT.TAGVALS", _boxedIndexName, fieldName).ConfigureAwait(false);
/// <summary> /// <summary>
/// Add a single document to the query /// Add a single document to the query
/// </summary> /// </summary>
/// <param name="docId">the id of the document. It cannot belong to a document already in the index unless replace is set</param> /// <param name="docId">the id of the document. It cannot belong to a document already in the index unless replace is set</param>
/// <param name="score">the document's score, floating point number between 0 and 1</param> /// <param name="score">the document's score, floating point number between 0 and 1</param>
/// <param name="fields">a map of the document's fields</param> /// <param name="fields">a map of the document's fields</param>
/// <param name="noSave">if set, we only index the document and do not save its contents. This allows fetching just doc ids</param> /// <param name="noSave">if set, we only index the document and do not save its contents. This allows fetching just doc ids</param>
/// <param name="replace">if set, and the document already exists, we reindex and update it</param> /// <param name="replace">if set, and the document already exists, we reindex and update it</param>
/// <param name="payload">if set, we can save a payload in the index to be retrieved or evaluated by scoring functions on the server</param> /// <param name="payload">if set, we can save a payload in the index to be retrieved or evaluated by scoring functions on the server</param>
public bool AddDocument(string docId, Dictionary<string, RedisValue> fields, double score = 1.0, bool noSave = false, bool replace = false, byte[] payload = null) public bool AddDocument(string docId, Dictionary<string, RedisValue> fields, double score = 1.0, bool noSave = false, bool replace = false, byte[] payload = null)
{ {
var args = BuildAddDocumentArgs(docId, fields, score, noSave, replace, payload); var args = BuildAddDocumentArgs(docId, fields, score, noSave, replace, payload);
return (string)DbSync.Execute("FT.ADD", args) == "OK"; return (string)DbSync.Execute("FT.ADD", args) == "OK";
} }
/// <summary> /// <summary>
/// Add a single document to the query /// Add a single document to the query
/// </summary> /// </summary>
/// <param name="docId">the id of the document. It cannot belong to a document already in the index unless replace is set</param> /// <param name="docId">the id of the document. It cannot belong to a document already in the index unless replace is set</param>
/// <param name="score">the document's score, floating point number between 0 and 1</param> /// <param name="score">the document's score, floating point number between 0 and 1</param>
/// <param name="fields">a map of the document's fields</param> /// <param name="fields">a map of the document's fields</param>
/// <param name="noSave">if set, we only index the document and do not save its contents. This allows fetching just doc ids</param> /// <param name="noSave">if set, we only index the document and do not save its contents. This allows fetching just doc ids</param>
/// <param name="replace">if set, and the document already exists, we reindex and update it</param> /// <param name="replace">if set, and the document already exists, we reindex and update it</param>
/// <param name="payload">if set, we can save a payload in the index to be retrieved or evaluated by scoring functions on the server</param> /// <param name="payload">if set, we can save a payload in the index to be retrieved or evaluated by scoring functions on the server</param>
public async Task<bool> AddDocumentAsync(string docId, Dictionary<string, RedisValue> fields, double score = 1.0, bool noSave = false, bool replace = false, byte[] payload = null) public async Task<bool> AddDocumentAsync(string docId, Dictionary<string, RedisValue> fields, double score = 1.0, bool noSave = false, bool replace = false, byte[] payload = null)
{ {
var args = BuildAddDocumentArgs(docId, fields, score, noSave, replace, payload); var args = BuildAddDocumentArgs(docId, fields, score, noSave, replace, payload);
return (string)await _db.ExecuteAsync("FT.ADD", args).ConfigureAwait(false) == "OK"; return (string)await _db.ExecuteAsync("FT.ADD", args).ConfigureAwait(false) == "OK";
} }
private List<object> BuildAddDocumentArgs(string docId, Dictionary<string, RedisValue> fields, double score, bool noSave, bool replace, byte[] payload) private List<object> BuildAddDocumentArgs(string docId, Dictionary<string, RedisValue> fields, double score, bool noSave, bool replace, byte[] payload)
{ {
var args = new List<object> { _boxedIndexName, docId, score }; var args = new List<object> { _boxedIndexName, docId, score };
if (noSave) if (noSave)
{ {
args.Add("NOSAVE".Literal()); args.Add("NOSAVE".Literal());
} }
if (replace) if (replace)
{ {
args.Add("REPLACE".Literal()); args.Add("REPLACE".Literal());
} }
if (payload != null) if (payload != null)
{ {
args.Add("PAYLOAD".Literal()); args.Add("PAYLOAD".Literal());
// TODO: Fix this // TODO: Fix this
args.Add(payload); args.Add(payload);
} }
args.Add("FIELDS".Literal()); args.Add("FIELDS".Literal());
foreach (var ent in fields) foreach (var ent in fields)
{ {
args.Add(ent.Key); args.Add(ent.Key);
args.Add(ent.Value); args.Add(ent.Value);
} }
return args; return args;
} }
/// <summary> /// <summary>
/// Convenience method for calling AddDocument with replace=true. /// Convenience method for calling AddDocument with replace=true.
/// </summary> /// </summary>
/// <param name="docId">The ID of the document to replce.</param> /// <param name="docId">The ID of the document to replce.</param>
/// <param name="fields">The document fields.</param> /// <param name="fields">The document fields.</param>
/// <param name="score">The new score.</param> /// <param name="score">The new score.</param>
/// <param name="payload">The new payload.</param> /// <param name="payload">The new payload.</param>
public bool ReplaceDocument(string docId, Dictionary<string, RedisValue> fields, double score = 1.0, byte[] payload = null) public bool ReplaceDocument(string docId, Dictionary<string, RedisValue> fields, double score = 1.0, byte[] payload = null)
=> AddDocument(docId, fields, score, false, true, payload); => AddDocument(docId, fields, score, false, true, payload);
/// <summary> /// <summary>
/// Convenience method for calling AddDocumentAsync with replace=true. /// Convenience method for calling AddDocumentAsync with replace=true.
/// </summary> /// </summary>
/// <param name="docId">The ID of the document to replce.</param> /// <param name="docId">The ID of the document to replce.</param>
/// <param name="fields">The document fields.</param> /// <param name="fields">The document fields.</param>
/// <param name="score">The new score.</param> /// <param name="score">The new score.</param>
/// <param name="payload">The new payload.</param> /// <param name="payload">The new payload.</param>
public Task<bool> ReplaceDocumentAsync(string docId, Dictionary<string, RedisValue> fields, double score = 1.0, byte[] payload = null) public Task<bool> ReplaceDocumentAsync(string docId, Dictionary<string, RedisValue> fields, double score = 1.0, byte[] payload = null)
=> AddDocumentAsync(docId, fields, score, false, true, payload); => AddDocumentAsync(docId, fields, score, false, true, payload);
/// <summary> /// <summary>
/// Index a document already in redis as a HASH key. /// Index a document already in redis as a HASH key.
/// </summary> /// </summary>
/// <param name="docId">the id of the document in redis. This must match an existing, unindexed HASH key</param> /// <param name="docId">the id of the document in redis. This must match an existing, unindexed HASH key</param>
/// <param name="score">the document's index score, between 0 and 1</param> /// <param name="score">the document's index score, between 0 and 1</param>
/// <param name="replace">if set, and the document already exists, we reindex and update it</param> /// <param name="replace">if set, and the document already exists, we reindex and update it</param>
/// <returns>true on success</returns> /// <returns>true on success</returns>
public bool AddHash(string docId, double score, bool replace) public bool AddHash(string docId, double score, bool replace)
{ {
var args = new List<object> { _boxedIndexName, docId, score }; var args = new List<object> { _boxedIndexName, docId, score };
if (replace) if (replace)
{ {
args.Add("REPLACE".Literal()); args.Add("REPLACE".Literal());
} }
return (string)DbSync.Execute("FT.ADDHASH", args) == "OK"; return (string)DbSync.Execute("FT.ADDHASH", args) == "OK";
} }
/// <summary> /// <summary>
/// Index a document already in redis as a HASH key. /// Index a document already in redis as a HASH key.
/// </summary> /// </summary>
/// <param name="docId">the id of the document in redis. This must match an existing, unindexed HASH key</param> /// <param name="docId">the id of the document in redis. This must match an existing, unindexed HASH key</param>
/// <param name="score">the document's index score, between 0 and 1</param> /// <param name="score">the document's index score, between 0 and 1</param>
/// <param name="replace">if set, and the document already exists, we reindex and update it</param> /// <param name="replace">if set, and the document already exists, we reindex and update it</param>
/// <returns>true on success</returns> /// <returns>true on success</returns>
public async Task<bool> AddHashAsync(string docId, double score, bool replace) public async Task<bool> AddHashAsync(string docId, double score, bool replace)
{ {
var args = new List<object> { _boxedIndexName, docId, score }; var args = new List<object> { _boxedIndexName, docId, score };
if (replace) if (replace)
{ {
args.Add("REPLACE".Literal()); args.Add("REPLACE".Literal());
} }
return (string)await _db.ExecuteAsync("FT.ADDHASH", args).ConfigureAwait(false) == "OK"; return (string)await _db.ExecuteAsync("FT.ADDHASH", args).ConfigureAwait(false) == "OK";
} }
/// <summary> /// <summary>
/// Get the index info, including memory consumption and other statistics. /// Get the index info, including memory consumption and other statistics.
/// </summary> /// </summary>
/// <remarks>TODO: Make a class for easier access to the index properties</remarks> /// <remarks>TODO: Make a class for easier access to the index properties</remarks>
/// <returns>a map of key/value pairs</returns> /// <returns>a map of key/value pairs</returns>
public Dictionary<string, RedisValue> GetInfo() => public Dictionary<string, RedisValue> GetInfo() =>
ParseGetInfo(DbSync.Execute("FT.INFO", _boxedIndexName)); ParseGetInfo(DbSync.Execute("FT.INFO", _boxedIndexName));
/// <summary> /// <summary>
/// Get the index info, including memory consumption and other statistics. /// Get the index info, including memory consumption and other statistics.
/// </summary> /// </summary>
/// <remarks>TODO: Make a class for easier access to the index properties</remarks> /// <remarks>TODO: Make a class for easier access to the index properties</remarks>
/// <returns>a map of key/value pairs</returns> /// <returns>a map of key/value pairs</returns>
public async Task<Dictionary<string, RedisValue>> GetInfoAsync() => public async Task<Dictionary<string, RedisValue>> GetInfoAsync() =>
ParseGetInfo(await _db.ExecuteAsync("FT.INFO", _boxedIndexName).ConfigureAwait(false)); ParseGetInfo(await _db.ExecuteAsync("FT.INFO", _boxedIndexName).ConfigureAwait(false));
private static Dictionary<string, RedisValue> ParseGetInfo(RedisResult value) private static Dictionary<string, RedisValue> ParseGetInfo(RedisResult value)
{ {
var res = (RedisValue[])value; var res = (RedisValue[])value;
var info = new Dictionary<string, RedisValue>(); var info = new Dictionary<string, RedisValue>();
for (int i = 0; i < res.Length; i += 2) for (int i = 0; i < res.Length; i += 2)
{ {
var key = (string)res[i]; var key = (string)res[i];
var val = res[i + 1]; var val = res[i + 1];
info.Add(key, val); info.Add(key, val);
} }
return info; return info;
} }
/// <summary> /// <summary>
/// Delete a document from the index. /// Delete a document from the index.
/// </summary> /// </summary>
/// <param name="docId">the document's id</param> /// <param name="docId">the document's id</param>
/// <returns>true if it has been deleted, false if it did not exist</returns> /// <returns>true if it has been deleted, false if it did not exist</returns>
public bool DeleteDocument(string docId) public bool DeleteDocument(string docId)
{ {
return (long)DbSync.Execute("FT.DEL", _boxedIndexName, docId) == 1; return (long)DbSync.Execute("FT.DEL", _boxedIndexName, docId) == 1;
} }
/// <summary> /// <summary>
/// Delete a document from the index. /// Delete a document from the index.
/// </summary> /// </summary>
/// <param name="docId">the document's id</param> /// <param name="docId">the document's id</param>
/// <returns>true if it has been deleted, false if it did not exist</returns> /// <returns>true if it has been deleted, false if it did not exist</returns>
public async Task<bool> DeleteDocumentAsync(string docId) public async Task<bool> DeleteDocumentAsync(string docId)
{ {
return (long)await _db.ExecuteAsync("FT.DEL", _boxedIndexName, docId).ConfigureAwait(false) == 1; return (long)await _db.ExecuteAsync("FT.DEL", _boxedIndexName, docId).ConfigureAwait(false) == 1;
} }
/// <summary> /// <summary>
/// Drop the index and all associated keys, including documents /// Drop the index and all associated keys, including documents
/// </summary> /// </summary>
/// <returns>true on success</returns> /// <returns>true on success</returns>
public bool DropIndex() public bool DropIndex()
{ {
return (string)DbSync.Execute("FT.DROP", _boxedIndexName) == "OK"; return (string)DbSync.Execute("FT.DROP", _boxedIndexName) == "OK";
} }
/// <summary> /// <summary>
/// Drop the index and all associated keys, including documents /// Drop the index and all associated keys, including documents
/// </summary> /// </summary>
/// <returns>true on success</returns> /// <returns>true on success</returns>
public async Task<bool> DropIndexAsync() public async Task<bool> DropIndexAsync()
{ {
return (string) await _db.ExecuteAsync("FT.DROP", _boxedIndexName).ConfigureAwait(false) == "OK"; return (string) await _db.ExecuteAsync("FT.DROP", _boxedIndexName).ConfigureAwait(false) == "OK";
} }
/// <summary> /// <summary>
/// Optimize memory consumption of the index by removing extra saved capacity. This does not affect speed /// Optimize memory consumption of the index by removing extra saved capacity. This does not affect speed
/// </summary> /// </summary>
public long OptimizeIndex() public long OptimizeIndex()
{ {
return (long)DbSync.Execute("FT.OPTIMIZE", _boxedIndexName); return (long)DbSync.Execute("FT.OPTIMIZE", _boxedIndexName);
} }
/// <summary> /// <summary>
/// Optimize memory consumption of the index by removing extra saved capacity. This does not affect speed /// Optimize memory consumption of the index by removing extra saved capacity. This does not affect speed
/// </summary> /// </summary>
public async Task<long> OptimizeIndexAsync() public async Task<long> OptimizeIndexAsync()
{ {
return (long) await _db.ExecuteAsync("FT.OPTIMIZE", _boxedIndexName).ConfigureAwait(false); return (long) await _db.ExecuteAsync("FT.OPTIMIZE", _boxedIndexName).ConfigureAwait(false);
} }
/// <summary> /// <summary>
/// Get the size of an autoc-complete suggestion dictionary /// Get the size of an autoc-complete suggestion dictionary
/// </summary> /// </summary>
public long CountSuggestions() public long CountSuggestions()
=> (long)DbSync.Execute("FT.SUGLEN", _boxedIndexName); => (long)DbSync.Execute("FT.SUGLEN", _boxedIndexName);
/// <summary> /// <summary>
/// Get the size of an autoc-complete suggestion dictionary /// Get the size of an autoc-complete suggestion dictionary
/// </summary> /// </summary>
public async Task<long> CountSuggestionsAsync() public async Task<long> CountSuggestionsAsync()
=> (long)await _db.ExecuteAsync("FT.SUGLEN", _boxedIndexName).ConfigureAwait(false); => (long)await _db.ExecuteAsync("FT.SUGLEN", _boxedIndexName).ConfigureAwait(false);
/// <summary> /// <summary>
/// Add a suggestion string to an auto-complete suggestion dictionary. This is disconnected from the index definitions, and leaves creating and updating suggestino dictionaries to the user. /// Add a suggestion string to an auto-complete suggestion dictionary. This is disconnected from the index definitions, and leaves creating and updating suggestino dictionaries to the user.
/// </summary> /// </summary>
/// <param name="value">the suggestion string we index</param> /// <param name="value">the suggestion string we index</param>
/// <param name="score">a floating point number of the suggestion string's weight</param> /// <param name="score">a floating point number of the suggestion string's weight</param>
/// <param name="increment">if set, we increment the existing entry of the suggestion by the given score, instead of replacing the score. This is useful for updating the dictionary based on user queries in real time</param> /// <param name="increment">if set, we increment the existing entry of the suggestion by the given score, instead of replacing the score. This is useful for updating the dictionary based on user queries in real time</param>
/// <returns>the current size of the suggestion dictionary.</returns> /// <returns>the current size of the suggestion dictionary.</returns>
public long AddSuggestion(string value, double score, bool increment = false) public long AddSuggestion(string value, double score, bool increment = false)
{ {
object args = increment object args = increment
? new object[] { _boxedIndexName, value, score, "INCR".Literal() } ? new object[] { _boxedIndexName, value, score, "INCR".Literal() }
: new object[] { _boxedIndexName, value, score }; : new object[] { _boxedIndexName, value, score };
return (long)DbSync.Execute("FT.SUGADD", args); return (long)DbSync.Execute("FT.SUGADD", args);
} }
/// <summary> /// <summary>
/// Add a suggestion string to an auto-complete suggestion dictionary. This is disconnected from the index definitions, and leaves creating and updating suggestino dictionaries to the user. /// Add a suggestion string to an auto-complete suggestion dictionary. This is disconnected from the index definitions, and leaves creating and updating suggestino dictionaries to the user.
/// </summary> /// </summary>
/// <param name="value">the suggestion string we index</param> /// <param name="value">the suggestion string we index</param>
/// <param name="score">a floating point number of the suggestion string's weight</param> /// <param name="score">a floating point number of the suggestion string's weight</param>
/// <param name="increment">if set, we increment the existing entry of the suggestion by the given score, instead of replacing the score. This is useful for updating the dictionary based on user queries in real time</param> /// <param name="increment">if set, we increment the existing entry of the suggestion by the given score, instead of replacing the score. This is useful for updating the dictionary based on user queries in real time</param>
/// <returns>the current size of the suggestion dictionary.</returns> /// <returns>the current size of the suggestion dictionary.</returns>
public async Task<long> AddSuggestionAsync(string value, double score, bool increment = false) public async Task<long> AddSuggestionAsync(string value, double score, bool increment = false)
{ {
object args = increment object args = increment
? new object[] { _boxedIndexName, value, score, "INCR".Literal() } ? new object[] { _boxedIndexName, value, score, "INCR".Literal() }
: new object[] { _boxedIndexName, value, score }; : new object[] { _boxedIndexName, value, score };
return (long)await _db.ExecuteAsync("FT.SUGADD", args).ConfigureAwait(false); return (long)await _db.ExecuteAsync("FT.SUGADD", args).ConfigureAwait(false);
} }
/// <summary> /// <summary>
/// Delete a string from a suggestion index. /// Delete a string from a suggestion index.
/// </summary> /// </summary>
/// <param name="value">the string to delete</param> /// <param name="value">the string to delete</param>
public bool DeleteSuggestion(string value) public bool DeleteSuggestion(string value)
=> (long)DbSync.Execute("FT.SUGDEL", _boxedIndexName, value) == 1; => (long)DbSync.Execute("FT.SUGDEL", _boxedIndexName, value) == 1;
/// <summary> /// <summary>
/// Delete a string from a suggestion index. /// Delete a string from a suggestion index.
/// </summary> /// </summary>
/// <param name="value">the string to delete</param> /// <param name="value">the string to delete</param>
public async Task<bool> DeleteSuggestionAsync(string value) public async Task<bool> DeleteSuggestionAsync(string value)
=> (long)await _db.ExecuteAsync("FT.SUGDEL", _boxedIndexName, value).ConfigureAwait(false) == 1; => (long)await _db.ExecuteAsync("FT.SUGDEL", _boxedIndexName, value).ConfigureAwait(false) == 1;
/// <summary> /// <summary>
/// Get completion suggestions for a prefix /// Get completion suggestions for a prefix
/// </summary> /// </summary>
/// <param name="prefix">the prefix to complete on</param> /// <param name="prefix">the prefix to complete on</param>
/// <param name="fuzzy"> if set,we do a fuzzy prefix search, including prefixes at levenshtein distance of 1 from the prefix sent</param> /// <param name="fuzzy"> if set,we do a fuzzy prefix search, including prefixes at levenshtein distance of 1 from the prefix sent</param>
/// <param name="max">If set, we limit the results to a maximum of num. (Note: The default is 5, and the number cannot be greater than 10).</param> /// <param name="max">If set, we limit the results to a maximum of num. (Note: The default is 5, and the number cannot be greater than 10).</param>
/// <returns>a list of the top suggestions matching the prefix</returns> /// <returns>a list of the top suggestions matching the prefix</returns>
public string[] GetSuggestions(string prefix, bool fuzzy = false, int max = 5) public string[] GetSuggestions(string prefix, bool fuzzy = false, int max = 5)
{ {
var args = new List<object> { _boxedIndexName, prefix}; var args = new List<object> { _boxedIndexName, prefix};
if (fuzzy) args.Add("FUZZY".Literal()); if (fuzzy) args.Add("FUZZY".Literal());
if (max != 5) if (max != 5)
{ {
args.Add("MAX".Literal()); args.Add("MAX".Literal());
args.Add(max); args.Add(max);
} }
return (string[])DbSync.Execute("FT.SUGGET", args); return (string[])DbSync.Execute("FT.SUGGET", args);
} }
/// <summary> /// <summary>
/// Get completion suggestions for a prefix /// Get completion suggestions for a prefix
/// </summary> /// </summary>
/// <param name="prefix">the prefix to complete on</param> /// <param name="prefix">the prefix to complete on</param>
/// <param name="fuzzy"> if set,we do a fuzzy prefix search, including prefixes at levenshtein distance of 1 from the prefix sent</param> /// <param name="fuzzy"> if set,we do a fuzzy prefix search, including prefixes at levenshtein distance of 1 from the prefix sent</param>
/// <param name="max">If set, we limit the results to a maximum of num. (Note: The default is 5, and the number cannot be greater than 10).</param> /// <param name="max">If set, we limit the results to a maximum of num. (Note: The default is 5, and the number cannot be greater than 10).</param>
/// <returns>a list of the top suggestions matching the prefix</returns> /// <returns>a list of the top suggestions matching the prefix</returns>
public async Task<string[]> GetSuggestionsAsync(string prefix, bool fuzzy = false, int max = 5) public async Task<string[]> GetSuggestionsAsync(string prefix, bool fuzzy = false, int max = 5)
{ {
var args = new List<object> { _boxedIndexName, prefix }; var args = new List<object> { _boxedIndexName, prefix };
if (fuzzy) args.Add("FUZZY".Literal()); if (fuzzy) args.Add("FUZZY".Literal());
if (max != 5) if (max != 5)
{ {
args.Add("MAX".Literal()); args.Add("MAX".Literal());
args.Add(max); args.Add(max);
} }
return (string[])await _db.ExecuteAsync("FT.SUGGET", args).ConfigureAwait(false); return (string[])await _db.ExecuteAsync("FT.SUGGET", args).ConfigureAwait(false);
} }
} }
} }
// .NET port of https://github.com/RedisLabs/JRediSearch/ // .NET port of https://github.com/RedisLabs/JRediSearch/
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace NRediSearch namespace NRediSearch
{ {
/// <summary> /// <summary>
/// Schema abstracts the schema definition when creating an index. /// Schema abstracts the schema definition when creating an index.
/// Documents can contain fields not mentioned in the schema, but the index will only index pre-defined fields /// Documents can contain fields not mentioned in the schema, but the index will only index pre-defined fields
/// </summary> /// </summary>
public sealed class Schema public sealed class Schema
{ {
public enum FieldType public enum FieldType
{ {
FullText, FullText,
Geo, Geo,
Numeric, Numeric,
Tag Tag
} }
public class Field public class Field
{ {
public String Name { get; } public String Name { get; }
public FieldType Type { get; } public FieldType Type { get; }
public bool Sortable {get;} public bool Sortable {get;}
internal Field(string name, FieldType type, bool sortable) internal Field(string name, FieldType type, bool sortable)
{ {
Name = name; Name = name;
Type = type; Type = type;
Sortable = sortable; Sortable = sortable;
} }
internal virtual void SerializeRedisArgs(List<object> args) internal virtual void SerializeRedisArgs(List<object> args)
{ {
object GetForRedis(FieldType type) object GetForRedis(FieldType type)
{ {
switch (type) switch (type)
{ {
case FieldType.FullText: return "TEXT".Literal(); case FieldType.FullText: return "TEXT".Literal();
case FieldType.Geo: return "GEO".Literal(); case FieldType.Geo: return "GEO".Literal();
case FieldType.Numeric: return "NUMERIC".Literal(); case FieldType.Numeric: return "NUMERIC".Literal();
case FieldType.Tag: return "TAG".Literal(); case FieldType.Tag: return "TAG".Literal();
default: throw new ArgumentOutOfRangeException(nameof(type)); default: throw new ArgumentOutOfRangeException(nameof(type));
} }
} }
args.Add(Name); args.Add(Name);
args.Add(GetForRedis(Type)); args.Add(GetForRedis(Type));
if(Sortable){args.Add("SORTABLE");} if(Sortable){args.Add("SORTABLE");}
} }
} }
public class TextField : Field public class TextField : Field
{ {
public double Weight { get; } public double Weight { get; }
internal TextField(string name, double weight = 1.0) : base(name, FieldType.FullText, false) internal TextField(string name, double weight = 1.0) : base(name, FieldType.FullText, false)
{ {
Weight = weight; Weight = weight;
} }
internal TextField(string name, bool sortable, double weight = 1.0) : base(name, FieldType.FullText, sortable) internal TextField(string name, bool sortable, double weight = 1.0) : base(name, FieldType.FullText, sortable)
{ {
Weight = weight; Weight = weight;
} }
internal override void SerializeRedisArgs(List<object> args) internal override void SerializeRedisArgs(List<object> args)
{ {
base.SerializeRedisArgs(args); base.SerializeRedisArgs(args);
if (Weight != 1.0) if (Weight != 1.0)
{ {
args.Add("WEIGHT".Literal()); args.Add("WEIGHT".Literal());
args.Add(Weight); args.Add(Weight);
} }
} }
} }
public List<Field> Fields { get; } = new List<Field>(); public List<Field> Fields { get; } = new List<Field>();
/// <summary> /// <summary>
/// Add a text field to the schema with a given weight /// Add a text field to the schema with a given weight
/// </summary> /// </summary>
/// <param name="name">the field's name</param> /// <param name="name">the field's name</param>
/// <param name="weight">its weight, a positive floating point number</param> /// <param name="weight">its weight, a positive floating point number</param>
/// <returns>the schema object</returns> /// <returns>the schema object</returns>
public Schema AddTextField(string name, double weight = 1.0) public Schema AddTextField(string name, double weight = 1.0)
{ {
Fields.Add(new TextField(name, weight)); Fields.Add(new TextField(name, weight));
return this; return this;
} }
/// <summary> /// <summary>
/// Add a text field that can be sorted on /// Add a text field that can be sorted on
/// </summary> /// </summary>
/// <param name="name">the field's name</param> /// <param name="name">the field's name</param>
/// <param name="weight">its weight, a positive floating point number</param> /// <param name="weight">its weight, a positive floating point number</param>
/// <returns>the schema object</returns> /// <returns>the schema object</returns>
public Schema AddSortableTextField(string name, double weight = 1.0) public Schema AddSortableTextField(string name, double weight = 1.0)
{ {
Fields.Add(new TextField(name, true, weight)); Fields.Add(new TextField(name, true, weight));
return this; return this;
} }
/// <summary> /// <summary>
/// Add a numeric field to the schema /// Add a numeric field to the schema
/// </summary> /// </summary>
/// <param name="name">the field's name</param> /// <param name="name">the field's name</param>
/// <returns>the schema object</returns> /// <returns>the schema object</returns>
public Schema AddGeoField(string name) public Schema AddGeoField(string name)
{ {
Fields.Add(new Field(name, FieldType.Geo, false)); Fields.Add(new Field(name, FieldType.Geo, false));
return this; return this;
} }
/// <summary> /// <summary>
/// Add a numeric field to the schema /// Add a numeric field to the schema
/// </summary> /// </summary>
/// <param name="name">the field's name</param> /// <param name="name">the field's name</param>
/// <returns>the schema object</returns> /// <returns>the schema object</returns>
public Schema AddNumericField(string name) public Schema AddNumericField(string name)
{ {
Fields.Add(new Field(name, FieldType.Numeric, false)); Fields.Add(new Field(name, FieldType.Numeric, false));
return this; return this;
} }
/// <summary> /// <summary>
/// Add a numeric field that can be sorted on /// Add a numeric field that can be sorted on
/// </summary> /// </summary>
/// <param name="name">the field's name</param> /// <param name="name">the field's name</param>
/// <returns>the schema object</returns> /// <returns>the schema object</returns>
public Schema AddSortableNumericField(string name) public Schema AddSortableNumericField(string name)
{ {
Fields.Add(new Field(name, FieldType.Numeric, true)); Fields.Add(new Field(name, FieldType.Numeric, true));
return this; return this;
} }
public class TagField : Field public class TagField : Field
{ {
public string Separator { get; } public string Separator { get; }
internal TagField(string name, string separator = ",") : base(name, FieldType.Tag, false) internal TagField(string name, string separator = ",") : base(name, FieldType.Tag, false)
{ {
Separator = separator; Separator = separator;
} }
internal override void SerializeRedisArgs(List<object> args) internal override void SerializeRedisArgs(List<object> args)
{ {
base.SerializeRedisArgs(args); base.SerializeRedisArgs(args);
if (Separator != ",") if (Separator != ",")
{ {
args.Add("SEPARATOR".Literal()); args.Add("SEPARATOR".Literal());
args.Add(Separator); args.Add(Separator);
} }
} }
} }
/// <summary> /// <summary>
/// Add a TAG field /// Add a TAG field
/// </summary> /// </summary>
/// <param name="name">the field's name</param> /// <param name="name">the field's name</param>
/// <param name="separator">tag separator</param> /// <param name="separator">tag separator</param>
/// <returns>the schema object</returns> /// <returns>the schema object</returns>
public Schema AddTagField(string name, string separator = ",") public Schema AddTagField(string name, string separator = ",")
{ {
Fields.Add(new TagField(name, separator)); Fields.Add(new TagField(name, separator));
return this; return this;
} }
} }
} }
using Moq; using Moq;
using StackExchange.Redis.KeyspaceIsolation; using StackExchange.Redis.KeyspaceIsolation;
using System.Text; using System.Text;
using Xunit; using Xunit;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
public sealed class BatchWrapperTests public sealed class BatchWrapperTests
{ {
private readonly Mock<IBatch> mock; private readonly Mock<IBatch> mock;
private readonly BatchWrapper wrapper; private readonly BatchWrapper wrapper;
public BatchWrapperTests() public BatchWrapperTests()
{ {
mock = new Mock<IBatch>(); mock = new Mock<IBatch>();
wrapper = new BatchWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:")); wrapper = new BatchWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
} }
[Fact] [Fact]
public void Execute() public void Execute()
{ {
wrapper.Execute(); wrapper.Execute();
mock.Verify(_ => _.Execute(), Times.Once()); mock.Verify(_ => _.Execute(), Times.Once());
} }
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
public class Cluster : TestBase public class Cluster : TestBase
{ {
public Cluster(ITestOutputHelper output) : base (output) { } public Cluster(ITestOutputHelper output) : base (output) { }
protected override string GetConfiguration() protected override string GetConfiguration()
{ {
var server = TestConfig.Current.ClusterServer; var server = TestConfig.Current.ClusterServer;
// TODO: Make TestConfig read JSON overrides in .gitignore here // TODO: Make TestConfig read JSON overrides in .gitignore here
if (string.Equals(Environment.MachineName, "MARC-LAPTOP", StringComparison.OrdinalIgnoreCase)) if (string.Equals(Environment.MachineName, "MARC-LAPTOP", StringComparison.OrdinalIgnoreCase))
{ {
server = "192.168.56.101"; server = "192.168.56.101";
} }
return string.Join(",", return string.Join(",",
from port in Enumerable.Range(TestConfig.Current.ClusterStartPort, TestConfig.Current.ClusterServerCount) from port in Enumerable.Range(TestConfig.Current.ClusterStartPort, TestConfig.Current.ClusterServerCount)
select server + ":" + port) + ",connectTimeout=10000"; select server + ":" + port) + ",connectTimeout=10000";
} }
[Fact] [Fact]
public void ExportConfiguration() public void ExportConfiguration()
{ {
if (File.Exists("cluster.zip")) File.Delete("cluster.zip"); if (File.Exists("cluster.zip")) File.Delete("cluster.zip");
Assert.False(File.Exists("cluster.zip")); Assert.False(File.Exists("cluster.zip"));
using (var muxer = Create(allowAdmin: true)) using (var muxer = Create(allowAdmin: true))
using (var file = File.Create("cluster.zip")) using (var file = File.Create("cluster.zip"))
{ {
muxer.ExportConfiguration(file); muxer.ExportConfiguration(file);
} }
Assert.True(File.Exists("cluster.zip")); Assert.True(File.Exists("cluster.zip"));
} }
[Fact] [Fact]
public void ConnectUsesSingleSocket() public void ConnectUsesSingleSocket()
{ {
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
using (var muxer = Create(failMessage: i + ": ")) using (var muxer = Create(failMessage: i + ": "))
{ {
foreach (var ep in muxer.GetEndPoints()) foreach (var ep in muxer.GetEndPoints())
{ {
var srv = muxer.GetServer(ep); var srv = muxer.GetServer(ep);
var counters = srv.GetCounters(); var counters = srv.GetCounters();
Output.WriteLine(i + "; interactive, " + ep); Output.WriteLine(i + "; interactive, " + ep);
Assert.Equal(1, counters.Interactive.SocketCount); Assert.Equal(1, counters.Interactive.SocketCount);
Output.WriteLine(i + "; subscription, " + ep); Output.WriteLine(i + "; subscription, " + ep);
Assert.Equal(1, counters.Subscription.SocketCount); Assert.Equal(1, counters.Subscription.SocketCount);
} }
} }
} }
} }
[Fact] [Fact]
public void CanGetTotalStats() public void CanGetTotalStats()
{ {
using (var muxer = Create()) using (var muxer = Create())
{ {
var counters = muxer.GetCounters(); var counters = muxer.GetCounters();
Output.WriteLine(counters.ToString()); Output.WriteLine(counters.ToString());
} }
} }
[Fact] [Fact]
public void Connect() public void Connect()
{ {
using (var muxer = Create()) using (var muxer = Create())
{ {
var endpoints = muxer.GetEndPoints(); var endpoints = muxer.GetEndPoints();
Assert.Equal(TestConfig.Current.ClusterServerCount, endpoints.Length); Assert.Equal(TestConfig.Current.ClusterServerCount, endpoints.Length);
var expectedPorts = new HashSet<int>(Enumerable.Range(TestConfig.Current.ClusterStartPort, TestConfig.Current.ClusterServerCount)); var expectedPorts = new HashSet<int>(Enumerable.Range(TestConfig.Current.ClusterStartPort, TestConfig.Current.ClusterServerCount));
int masters = 0, slaves = 0; int masters = 0, slaves = 0;
var failed = new List<EndPoint>(); var failed = new List<EndPoint>();
foreach (var endpoint in endpoints) foreach (var endpoint in endpoints)
{ {
var server = muxer.GetServer(endpoint); var server = muxer.GetServer(endpoint);
if (!server.IsConnected) if (!server.IsConnected)
{ {
failed.Add(endpoint); failed.Add(endpoint);
} }
Output.WriteLine("endpoint:" + endpoint); Output.WriteLine("endpoint:" + endpoint);
Assert.Equal(endpoint, server.EndPoint); Assert.Equal(endpoint, server.EndPoint);
Output.WriteLine("endpoint-type:" + endpoint); Output.WriteLine("endpoint-type:" + endpoint);
Assert.IsType<IPEndPoint>(endpoint); Assert.IsType<IPEndPoint>(endpoint);
Output.WriteLine("port:" + endpoint); Output.WriteLine("port:" + endpoint);
Assert.True(expectedPorts.Remove(((IPEndPoint)endpoint).Port)); Assert.True(expectedPorts.Remove(((IPEndPoint)endpoint).Port));
Output.WriteLine("server-type:" + endpoint); Output.WriteLine("server-type:" + endpoint);
Assert.Equal(ServerType.Cluster, server.ServerType); Assert.Equal(ServerType.Cluster, server.ServerType);
if (server.IsSlave) slaves++; if (server.IsSlave) slaves++;
else masters++; else masters++;
} }
if (failed.Count != 0) if (failed.Count != 0)
{ {
Output.WriteLine("{0} failues", failed.Count); Output.WriteLine("{0} failues", failed.Count);
foreach (var fail in failed) foreach (var fail in failed)
{ {
Output.WriteLine(fail.ToString()); Output.WriteLine(fail.ToString());
} }
Assert.True(false, "not all servers connected"); Assert.True(false, "not all servers connected");
} }
Assert.Equal(TestConfig.Current.ClusterServerCount / 2, slaves); Assert.Equal(TestConfig.Current.ClusterServerCount / 2, slaves);
Assert.Equal(TestConfig.Current.ClusterServerCount / 2, masters); Assert.Equal(TestConfig.Current.ClusterServerCount / 2, masters);
} }
} }
[Fact] [Fact]
public void TestIdentity() public void TestIdentity()
{ {
using (var conn = Create()) using (var conn = Create())
{ {
RedisKey key = Guid.NewGuid().ToByteArray(); RedisKey key = Guid.NewGuid().ToByteArray();
var ep = conn.GetDatabase().IdentifyEndpoint(key); var ep = conn.GetDatabase().IdentifyEndpoint(key);
Assert.Equal(ep, conn.GetServer(ep).ClusterConfiguration.GetBySlot(key).EndPoint); Assert.Equal(ep, conn.GetServer(ep).ClusterConfiguration.GetBySlot(key).EndPoint);
} }
} }
[Fact] [Fact]
public void IntentionalWrongServer() public void IntentionalWrongServer()
{ {
using (var conn = Create()) using (var conn = Create())
{ {
var endpoints = conn.GetEndPoints(); var endpoints = conn.GetEndPoints();
var servers = endpoints.Select(e => conn.GetServer(e)); var servers = endpoints.Select(e => conn.GetServer(e));
var key = Me(); var key = Me();
const string value = "abc"; const string value = "abc";
var db = conn.GetDatabase(); var db = conn.GetDatabase();
db.KeyDelete(key); db.KeyDelete(key);
db.StringSet(key, value); db.StringSet(key, value);
servers.First().Ping(); servers.First().Ping();
var config = servers.First().ClusterConfiguration; var config = servers.First().ClusterConfiguration;
Assert.NotNull(config); Assert.NotNull(config);
int slot = conn.HashSlot(key); int slot = conn.HashSlot(key);
var rightMasterNode = config.GetBySlot(key); var rightMasterNode = config.GetBySlot(key);
Assert.NotNull(rightMasterNode); Assert.NotNull(rightMasterNode);
Output.WriteLine("Right Master: {0} {1}", rightMasterNode.EndPoint, rightMasterNode.NodeId); Output.WriteLine("Right Master: {0} {1}", rightMasterNode.EndPoint, rightMasterNode.NodeId);
#if DEBUG #if DEBUG
string a = conn.GetServer(rightMasterNode.EndPoint).StringGet(db.Database, key); string a = conn.GetServer(rightMasterNode.EndPoint).StringGet(db.Database, key);
Assert.Equal(value, a); // right master Assert.Equal(value, a); // right master
var node = config.Nodes.FirstOrDefault(x => !x.IsSlave && x.NodeId != rightMasterNode.NodeId); var node = config.Nodes.FirstOrDefault(x => !x.IsSlave && x.NodeId != rightMasterNode.NodeId);
Assert.NotNull(node); Assert.NotNull(node);
Output.WriteLine("Using Master: {0}", node.EndPoint, node.NodeId); Output.WriteLine("Using Master: {0}", node.EndPoint, node.NodeId);
if (node != null) if (node != null)
{ {
string b = conn.GetServer(node.EndPoint).StringGet(db.Database, key); string b = conn.GetServer(node.EndPoint).StringGet(db.Database, key);
Assert.Equal(value, b); // wrong master, allow redirect Assert.Equal(value, b); // wrong master, allow redirect
var ex = Assert.Throws<RedisServerException>(() => conn.GetServer(node.EndPoint).StringGet(db.Database, key, CommandFlags.NoRedirect)); var ex = Assert.Throws<RedisServerException>(() => conn.GetServer(node.EndPoint).StringGet(db.Database, key, CommandFlags.NoRedirect));
Assert.StartsWith($"Key has MOVED from Endpoint {rightMasterNode.EndPoint} and hashslot {slot}", ex.Message); Assert.StartsWith($"Key has MOVED from Endpoint {rightMasterNode.EndPoint} and hashslot {slot}", ex.Message);
} }
node = config.Nodes.FirstOrDefault(x => x.IsSlave && x.ParentNodeId == rightMasterNode.NodeId); node = config.Nodes.FirstOrDefault(x => x.IsSlave && x.ParentNodeId == rightMasterNode.NodeId);
Assert.NotNull(node); Assert.NotNull(node);
if (node != null) if (node != null)
{ {
string d = conn.GetServer(node.EndPoint).StringGet(db.Database, key); string d = conn.GetServer(node.EndPoint).StringGet(db.Database, key);
Assert.Equal(value, d); // right slave Assert.Equal(value, d); // right slave
} }
node = config.Nodes.FirstOrDefault(x => x.IsSlave && x.ParentNodeId != rightMasterNode.NodeId); node = config.Nodes.FirstOrDefault(x => x.IsSlave && x.ParentNodeId != rightMasterNode.NodeId);
Assert.NotNull(node); Assert.NotNull(node);
if (node != null) if (node != null)
{ {
string e = conn.GetServer(node.EndPoint).StringGet(db.Database, key); string e = conn.GetServer(node.EndPoint).StringGet(db.Database, key);
Assert.Equal(value, e); // wrong slave, allow redirect Assert.Equal(value, e); // wrong slave, allow redirect
var ex = Assert.Throws<RedisServerException>(() => conn.GetServer(node.EndPoint).StringGet(db.Database, key, CommandFlags.NoRedirect)); var ex = Assert.Throws<RedisServerException>(() => conn.GetServer(node.EndPoint).StringGet(db.Database, key, CommandFlags.NoRedirect));
Assert.StartsWith($"Key has MOVED from Endpoint {rightMasterNode.EndPoint} and hashslot {slot}", ex.Message); Assert.StartsWith($"Key has MOVED from Endpoint {rightMasterNode.EndPoint} and hashslot {slot}", ex.Message);
} }
#endif #endif
} }
} }
[Fact] [Fact]
public void TransactionWithMultiServerKeys() public void TransactionWithMultiServerKeys()
{ {
var ex = Assert.Throws<RedisCommandException>(() => var ex = Assert.Throws<RedisCommandException>(() =>
{ {
using (var muxer = Create()) using (var muxer = Create())
{ {
// connect // connect
var cluster = muxer.GetDatabase(); var cluster = muxer.GetDatabase();
var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]); var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]);
anyServer.Ping(); anyServer.Ping();
Assert.Equal(ServerType.Cluster, anyServer.ServerType); Assert.Equal(ServerType.Cluster, anyServer.ServerType);
var config = anyServer.ClusterConfiguration; var config = anyServer.ClusterConfiguration;
Assert.NotNull(config); Assert.NotNull(config);
// invent 2 keys that we believe are served by different nodes // invent 2 keys that we believe are served by different nodes
string x = Guid.NewGuid().ToString(), y; string x = Guid.NewGuid().ToString(), y;
var xNode = config.GetBySlot(x); var xNode = config.GetBySlot(x);
int abort = 1000; int abort = 1000;
do do
{ {
y = Guid.NewGuid().ToString(); y = Guid.NewGuid().ToString();
} while (--abort > 0 && config.GetBySlot(y) == xNode); } while (--abort > 0 && config.GetBySlot(y) == xNode);
if (abort == 0) Skip.Inconclusive("failed to find a different node to use"); if (abort == 0) Skip.Inconclusive("failed to find a different node to use");
var yNode = config.GetBySlot(y); var yNode = config.GetBySlot(y);
Output.WriteLine("x={0}, served by {1}", x, xNode.NodeId); Output.WriteLine("x={0}, served by {1}", x, xNode.NodeId);
Output.WriteLine("y={0}, served by {1}", y, yNode.NodeId); Output.WriteLine("y={0}, served by {1}", y, yNode.NodeId);
Assert.NotEqual(xNode.NodeId, yNode.NodeId); Assert.NotEqual(xNode.NodeId, yNode.NodeId);
// wipe those keys // wipe those keys
cluster.KeyDelete(x, CommandFlags.FireAndForget); cluster.KeyDelete(x, CommandFlags.FireAndForget);
cluster.KeyDelete(y, CommandFlags.FireAndForget); cluster.KeyDelete(y, CommandFlags.FireAndForget);
// create a transaction that attempts to assign both keys // create a transaction that attempts to assign both keys
var tran = cluster.CreateTransaction(); var tran = cluster.CreateTransaction();
tran.AddCondition(Condition.KeyNotExists(x)); tran.AddCondition(Condition.KeyNotExists(x));
tran.AddCondition(Condition.KeyNotExists(y)); tran.AddCondition(Condition.KeyNotExists(y));
var setX = tran.StringSetAsync(x, "x-val"); var setX = tran.StringSetAsync(x, "x-val");
var setY = tran.StringSetAsync(y, "y-val"); var setY = tran.StringSetAsync(y, "y-val");
bool success = tran.Execute(); bool success = tran.Execute();
Assert.True(false, "Expected single-slot rules to apply"); Assert.True(false, "Expected single-slot rules to apply");
// the rest no longer applies while we are following single-slot rules // the rest no longer applies while we are following single-slot rules
//// check that everything was aborted //// check that everything was aborted
//Assert.False(success, "tran aborted"); //Assert.False(success, "tran aborted");
//Assert.True(setX.IsCanceled, "set x cancelled"); //Assert.True(setX.IsCanceled, "set x cancelled");
//Assert.True(setY.IsCanceled, "set y cancelled"); //Assert.True(setY.IsCanceled, "set y cancelled");
//var existsX = cluster.KeyExistsAsync(x); //var existsX = cluster.KeyExistsAsync(x);
//var existsY = cluster.KeyExistsAsync(y); //var existsY = cluster.KeyExistsAsync(y);
//Assert.False(cluster.Wait(existsX), "x exists"); //Assert.False(cluster.Wait(existsX), "x exists");
//Assert.False(cluster.Wait(existsY), "y exists"); //Assert.False(cluster.Wait(existsY), "y exists");
} }
}); });
Assert.Equal("Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot", ex.Message); Assert.Equal("Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot", ex.Message);
} }
[Fact] [Fact]
public void TransactionWithSameServerKeys() public void TransactionWithSameServerKeys()
{ {
var ex = Assert.Throws<RedisCommandException>(() => var ex = Assert.Throws<RedisCommandException>(() =>
{ {
using (var muxer = Create()) using (var muxer = Create())
{ {
// connect // connect
var cluster = muxer.GetDatabase(); var cluster = muxer.GetDatabase();
var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]); var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]);
anyServer.Ping(); anyServer.Ping();
var config = anyServer.ClusterConfiguration; var config = anyServer.ClusterConfiguration;
Assert.NotNull(config); Assert.NotNull(config);
// invent 2 keys that we believe are served by different nodes // invent 2 keys that we believe are served by different nodes
string x = Guid.NewGuid().ToString(), y; string x = Guid.NewGuid().ToString(), y;
var xNode = config.GetBySlot(x); var xNode = config.GetBySlot(x);
int abort = 1000; int abort = 1000;
do do
{ {
y = Guid.NewGuid().ToString(); y = Guid.NewGuid().ToString();
} while (--abort > 0 && config.GetBySlot(y) != xNode); } while (--abort > 0 && config.GetBySlot(y) != xNode);
if (abort == 0) Skip.Inconclusive("failed to find a key with the same node to use"); if (abort == 0) Skip.Inconclusive("failed to find a key with the same node to use");
var yNode = config.GetBySlot(y); var yNode = config.GetBySlot(y);
Output.WriteLine("x={0}, served by {1}", x, xNode.NodeId); Output.WriteLine("x={0}, served by {1}", x, xNode.NodeId);
Output.WriteLine("y={0}, served by {1}", y, yNode.NodeId); Output.WriteLine("y={0}, served by {1}", y, yNode.NodeId);
Assert.Equal(xNode.NodeId, yNode.NodeId); Assert.Equal(xNode.NodeId, yNode.NodeId);
// wipe those keys // wipe those keys
cluster.KeyDelete(x, CommandFlags.FireAndForget); cluster.KeyDelete(x, CommandFlags.FireAndForget);
cluster.KeyDelete(y, CommandFlags.FireAndForget); cluster.KeyDelete(y, CommandFlags.FireAndForget);
// create a transaction that attempts to assign both keys // create a transaction that attempts to assign both keys
var tran = cluster.CreateTransaction(); var tran = cluster.CreateTransaction();
tran.AddCondition(Condition.KeyNotExists(x)); tran.AddCondition(Condition.KeyNotExists(x));
tran.AddCondition(Condition.KeyNotExists(y)); tran.AddCondition(Condition.KeyNotExists(y));
var setX = tran.StringSetAsync(x, "x-val"); var setX = tran.StringSetAsync(x, "x-val");
var setY = tran.StringSetAsync(y, "y-val"); var setY = tran.StringSetAsync(y, "y-val");
bool success = tran.Execute(); bool success = tran.Execute();
Assert.True(false, "Expected single-slot rules to apply"); Assert.True(false, "Expected single-slot rules to apply");
// the rest no longer applies while we are following single-slot rules // the rest no longer applies while we are following single-slot rules
//// check that everything was aborted //// check that everything was aborted
//Assert.True(success, "tran aborted"); //Assert.True(success, "tran aborted");
//Assert.False(setX.IsCanceled, "set x cancelled"); //Assert.False(setX.IsCanceled, "set x cancelled");
//Assert.False(setY.IsCanceled, "set y cancelled"); //Assert.False(setY.IsCanceled, "set y cancelled");
//var existsX = cluster.KeyExistsAsync(x); //var existsX = cluster.KeyExistsAsync(x);
//var existsY = cluster.KeyExistsAsync(y); //var existsY = cluster.KeyExistsAsync(y);
//Assert.True(cluster.Wait(existsX), "x exists"); //Assert.True(cluster.Wait(existsX), "x exists");
//Assert.True(cluster.Wait(existsY), "y exists"); //Assert.True(cluster.Wait(existsY), "y exists");
} }
}); });
Assert.Equal("Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot", ex.Message); Assert.Equal("Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot", ex.Message);
} }
[Fact] [Fact]
public void TransactionWithSameSlotKeys() public void TransactionWithSameSlotKeys()
{ {
using (var muxer = Create()) using (var muxer = Create())
{ {
// connect // connect
var cluster = muxer.GetDatabase(); var cluster = muxer.GetDatabase();
var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]); var anyServer = muxer.GetServer(muxer.GetEndPoints()[0]);
anyServer.Ping(); anyServer.Ping();
var config = anyServer.ClusterConfiguration; var config = anyServer.ClusterConfiguration;
Assert.NotNull(config); Assert.NotNull(config);
// invent 2 keys that we believe are in the same slot // invent 2 keys that we believe are in the same slot
var guid = Guid.NewGuid().ToString(); var guid = Guid.NewGuid().ToString();
string x = "/{" + guid + "}/foo", y = "/{" + guid + "}/bar"; string x = "/{" + guid + "}/foo", y = "/{" + guid + "}/bar";
Assert.Equal(muxer.HashSlot(x), muxer.HashSlot(y)); Assert.Equal(muxer.HashSlot(x), muxer.HashSlot(y));
var xNode = config.GetBySlot(x); var xNode = config.GetBySlot(x);
var yNode = config.GetBySlot(y); var yNode = config.GetBySlot(y);
Output.WriteLine("x={0}, served by {1}", x, xNode.NodeId); Output.WriteLine("x={0}, served by {1}", x, xNode.NodeId);
Output.WriteLine("y={0}, served by {1}", y, yNode.NodeId); Output.WriteLine("y={0}, served by {1}", y, yNode.NodeId);
Assert.Equal(xNode.NodeId, yNode.NodeId); Assert.Equal(xNode.NodeId, yNode.NodeId);
// wipe those keys // wipe those keys
cluster.KeyDelete(x, CommandFlags.FireAndForget); cluster.KeyDelete(x, CommandFlags.FireAndForget);
cluster.KeyDelete(y, CommandFlags.FireAndForget); cluster.KeyDelete(y, CommandFlags.FireAndForget);
// create a transaction that attempts to assign both keys // create a transaction that attempts to assign both keys
var tran = cluster.CreateTransaction(); var tran = cluster.CreateTransaction();
tran.AddCondition(Condition.KeyNotExists(x)); tran.AddCondition(Condition.KeyNotExists(x));
tran.AddCondition(Condition.KeyNotExists(y)); tran.AddCondition(Condition.KeyNotExists(y));
var setX = tran.StringSetAsync(x, "x-val"); var setX = tran.StringSetAsync(x, "x-val");
var setY = tran.StringSetAsync(y, "y-val"); var setY = tran.StringSetAsync(y, "y-val");
bool success = tran.Execute(); bool success = tran.Execute();
// check that everything was aborted // check that everything was aborted
Assert.True(success, "tran aborted"); Assert.True(success, "tran aborted");
Assert.False(setX.IsCanceled, "set x cancelled"); Assert.False(setX.IsCanceled, "set x cancelled");
Assert.False(setY.IsCanceled, "set y cancelled"); Assert.False(setY.IsCanceled, "set y cancelled");
var existsX = cluster.KeyExistsAsync(x); var existsX = cluster.KeyExistsAsync(x);
var existsY = cluster.KeyExistsAsync(y); var existsY = cluster.KeyExistsAsync(y);
Assert.True(cluster.Wait(existsX), "x exists"); Assert.True(cluster.Wait(existsX), "x exists");
Assert.True(cluster.Wait(existsY), "y exists"); Assert.True(cluster.Wait(existsY), "y exists");
} }
} }
[Theory] [Theory]
[InlineData(null, 10)] [InlineData(null, 10)]
[InlineData(null, 100)] [InlineData(null, 100)]
[InlineData("abc", 10)] [InlineData("abc", 10)]
[InlineData("abc", 100)] [InlineData("abc", 100)]
public void Keys(string pattern, int pageSize) public void Keys(string pattern, int pageSize)
{ {
using (var conn = Create(allowAdmin: true)) using (var conn = Create(allowAdmin: true))
{ {
var cluster = conn.GetDatabase(); var cluster = conn.GetDatabase();
var server = conn.GetEndPoints().Select(x => conn.GetServer(x)).First(x => !x.IsSlave); var server = conn.GetEndPoints().Select(x => conn.GetServer(x)).First(x => !x.IsSlave);
server.FlushAllDatabases(); server.FlushAllDatabases();
try try
{ {
Assert.False(server.Keys(pattern: pattern, pageSize: pageSize).Any()); Assert.False(server.Keys(pattern: pattern, pageSize: pageSize).Any());
Output.WriteLine("Complete: '{0}' / {1}", pattern, pageSize); Output.WriteLine("Complete: '{0}' / {1}", pattern, pageSize);
} }
catch catch
{ {
Output.WriteLine("Failed: '{0}' / {1}", pattern, pageSize); Output.WriteLine("Failed: '{0}' / {1}", pattern, pageSize);
throw; throw;
} }
} }
} }
[Theory] [Theory]
[InlineData("", 0)] [InlineData("", 0)]
[InlineData("abc", 7638)] [InlineData("abc", 7638)]
[InlineData("{abc}", 7638)] [InlineData("{abc}", 7638)]
[InlineData("abcdef", 15101)] [InlineData("abcdef", 15101)]
[InlineData("abc{abc}def", 7638)] [InlineData("abc{abc}def", 7638)]
[InlineData("c", 7365)] [InlineData("c", 7365)]
[InlineData("g", 7233)] [InlineData("g", 7233)]
[InlineData("d", 11298)] [InlineData("d", 11298)]
[InlineData("user1000", 3443)] [InlineData("user1000", 3443)]
[InlineData("{user1000}", 3443)] [InlineData("{user1000}", 3443)]
[InlineData("abc{user1000}", 3443)] [InlineData("abc{user1000}", 3443)]
[InlineData("abc{user1000}def", 3443)] [InlineData("abc{user1000}def", 3443)]
[InlineData("{user1000}.following", 3443)] [InlineData("{user1000}.following", 3443)]
[InlineData("{user1000}.followers", 3443)] [InlineData("{user1000}.followers", 3443)]
[InlineData("foo{}{bar}", 8363)] [InlineData("foo{}{bar}", 8363)]
[InlineData("foo{{bar}}zap", 4015)] [InlineData("foo{{bar}}zap", 4015)]
[InlineData("{bar", 4015)] [InlineData("{bar", 4015)]
[InlineData("foo{bar}{zap}", 5061)] [InlineData("foo{bar}{zap}", 5061)]
[InlineData("bar", 5061)] [InlineData("bar", 5061)]
public void HashSlots(string key, int slot) public void HashSlots(string key, int slot)
{ {
using (var muxer = Create(connectTimeout: 500, pause: false)) using (var muxer = Create(connectTimeout: 500, pause: false))
{ {
Assert.Equal(slot, muxer.HashSlot(key)); Assert.Equal(slot, muxer.HashSlot(key));
} }
} }
[Fact] [Fact]
public void SScan() public void SScan()
{ {
using (var conn = Create()) using (var conn = Create())
{ {
RedisKey key = "a"; RedisKey key = "a";
var db = conn.GetDatabase(); var db = conn.GetDatabase();
db.KeyDelete(key); db.KeyDelete(key);
int totalUnfiltered = 0, totalFiltered = 0; int totalUnfiltered = 0, totalFiltered = 0;
for (int i = 0; i < 1000; i++) for (int i = 0; i < 1000; i++)
{ {
db.SetAdd(key, i); db.SetAdd(key, i);
totalUnfiltered += i; totalUnfiltered += i;
if (i.ToString().Contains("3")) totalFiltered += i; if (i.ToString().Contains("3")) totalFiltered += i;
} }
var unfilteredActual = db.SetScan(key).Select(x => (int)x).Sum(); var unfilteredActual = db.SetScan(key).Select(x => (int)x).Sum();
var filteredActual = db.SetScan(key, "*3*").Select(x => (int)x).Sum(); var filteredActual = db.SetScan(key, "*3*").Select(x => (int)x).Sum();
Assert.Equal(totalUnfiltered, unfilteredActual); Assert.Equal(totalUnfiltered, unfilteredActual);
Assert.Equal(totalFiltered, filteredActual); Assert.Equal(totalFiltered, filteredActual);
} }
} }
[Fact] [Fact]
public void GetConfig() public void GetConfig()
{ {
using (var muxer = Create(allowAdmin: true)) using (var muxer = Create(allowAdmin: true))
{ {
var endpoints = muxer.GetEndPoints(); var endpoints = muxer.GetEndPoints();
var server = muxer.GetServer(endpoints[0]); var server = muxer.GetServer(endpoints[0]);
var nodes = server.ClusterNodes(); var nodes = server.ClusterNodes();
Assert.Equal(endpoints.Length, nodes.Nodes.Count); Assert.Equal(endpoints.Length, nodes.Nodes.Count);
foreach (var node in nodes.Nodes.OrderBy(x => x)) foreach (var node in nodes.Nodes.OrderBy(x => x))
{ {
Output.WriteLine(node.ToString()); Output.WriteLine(node.ToString());
} }
} }
} }
[Fact] [Fact]
public void AccessRandomKeys() public void AccessRandomKeys()
{ {
using (var conn = Create(allowAdmin: true)) using (var conn = Create(allowAdmin: true))
{ {
var cluster = conn.GetDatabase(); var cluster = conn.GetDatabase();
int slotMovedCount = 0; int slotMovedCount = 0;
conn.HashSlotMoved += (s, a) => conn.HashSlotMoved += (s, a) =>
{ {
Output.WriteLine("{0} moved from {1} to {2}", a.HashSlot, Describe(a.OldEndPoint), Describe(a.NewEndPoint)); Output.WriteLine("{0} moved from {1} to {2}", a.HashSlot, Describe(a.OldEndPoint), Describe(a.NewEndPoint));
Interlocked.Increment(ref slotMovedCount); Interlocked.Increment(ref slotMovedCount);
}; };
var pairs = new Dictionary<string, string>(); var pairs = new Dictionary<string, string>();
const int COUNT = 500; const int COUNT = 500;
Task[] send = new Task[COUNT]; Task[] send = new Task[COUNT];
int index = 0; int index = 0;
var servers = conn.GetEndPoints().Select(x => conn.GetServer(x)); var servers = conn.GetEndPoints().Select(x => conn.GetServer(x));
foreach (var server in servers) foreach (var server in servers)
{ {
if (!server.IsSlave) if (!server.IsSlave)
{ {
server.Ping(); server.Ping();
server.FlushAllDatabases(); server.FlushAllDatabases();
} }
} }
for (int i = 0; i < COUNT; i++) for (int i = 0; i < COUNT; i++)
{ {
var key = Guid.NewGuid().ToString(); var key = Guid.NewGuid().ToString();
var value = Guid.NewGuid().ToString(); var value = Guid.NewGuid().ToString();
pairs.Add(key, value); pairs.Add(key, value);
send[index++] = cluster.StringSetAsync(key, value); send[index++] = cluster.StringSetAsync(key, value);
} }
conn.WaitAll(send); conn.WaitAll(send);
var expected = new string[COUNT]; var expected = new string[COUNT];
var actual = new Task<RedisValue>[COUNT]; var actual = new Task<RedisValue>[COUNT];
index = 0; index = 0;
foreach (var pair in pairs) foreach (var pair in pairs)
{ {
expected[index] = pair.Value; expected[index] = pair.Value;
actual[index] = cluster.StringGetAsync(pair.Key); actual[index] = cluster.StringGetAsync(pair.Key);
index++; index++;
} }
cluster.WaitAll(actual); cluster.WaitAll(actual);
for (int i = 0; i < COUNT; i++) for (int i = 0; i < COUNT; i++)
{ {
Assert.Equal(expected[i], (string)actual[i].Result); Assert.Equal(expected[i], (string)actual[i].Result);
} }
int total = 0; int total = 0;
Parallel.ForEach(servers, server => Parallel.ForEach(servers, server =>
{ {
if (!server.IsSlave) if (!server.IsSlave)
{ {
int count = server.Keys(pageSize: 100).Count(); int count = server.Keys(pageSize: 100).Count();
Output.WriteLine("{0} has {1} keys", server.EndPoint, count); Output.WriteLine("{0} has {1} keys", server.EndPoint, count);
Interlocked.Add(ref total, count); Interlocked.Add(ref total, count);
} }
}); });
foreach (var server in servers) foreach (var server in servers)
{ {
var counters = server.GetCounters(); var counters = server.GetCounters();
Output.WriteLine(counters.ToString()); Output.WriteLine(counters.ToString());
} }
int final = Interlocked.CompareExchange(ref total, 0, 0); int final = Interlocked.CompareExchange(ref total, 0, 0);
Assert.Equal(COUNT, final); Assert.Equal(COUNT, final);
Assert.Equal(0, Interlocked.CompareExchange(ref slotMovedCount, 0, 0)); Assert.Equal(0, Interlocked.CompareExchange(ref slotMovedCount, 0, 0));
} }
} }
[Theory] [Theory]
[InlineData(CommandFlags.DemandMaster, false)] [InlineData(CommandFlags.DemandMaster, false)]
[InlineData(CommandFlags.DemandSlave, true)] [InlineData(CommandFlags.DemandSlave, true)]
[InlineData(CommandFlags.PreferMaster, false)] [InlineData(CommandFlags.PreferMaster, false)]
[InlineData(CommandFlags.PreferSlave, true)] [InlineData(CommandFlags.PreferSlave, true)]
public void GetFromRightNodeBasedOnFlags(CommandFlags flags, bool isSlave) public void GetFromRightNodeBasedOnFlags(CommandFlags flags, bool isSlave)
{ {
using (var muxer = Create(allowAdmin: true)) using (var muxer = Create(allowAdmin: true))
{ {
var db = muxer.GetDatabase(); var db = muxer.GetDatabase();
for (int i = 0; i < 1000; i++) for (int i = 0; i < 1000; i++)
{ {
var key = Guid.NewGuid().ToString(); var key = Guid.NewGuid().ToString();
var endpoint = db.IdentifyEndpoint(key, flags); var endpoint = db.IdentifyEndpoint(key, flags);
var server = muxer.GetServer(endpoint); var server = muxer.GetServer(endpoint);
Output.WriteLine("Comparing: key"); Output.WriteLine("Comparing: key");
Assert.Equal(isSlave, server.IsSlave); Assert.Equal(isSlave, server.IsSlave);
} }
} }
} }
private static string Describe(EndPoint endpoint) => endpoint?.ToString() ?? "(unknown)"; private static string Describe(EndPoint endpoint) => endpoint?.ToString() ?? "(unknown)";
private class TestProfiler : IProfiler private class TestProfiler : IProfiler
{ {
public object MyContext = new object(); public object MyContext = new object();
public object GetContext() => MyContext; public object GetContext() => MyContext;
} }
[Fact] [Fact]
public void SimpleProfiling() public void SimpleProfiling()
{ {
using (var conn = Create()) using (var conn = Create())
{ {
var profiler = new TestProfiler(); var profiler = new TestProfiler();
conn.RegisterProfiler(profiler); conn.RegisterProfiler(profiler);
conn.BeginProfiling(profiler.MyContext); conn.BeginProfiling(profiler.MyContext);
var db = conn.GetDatabase(); var db = conn.GetDatabase();
db.StringSet("hello", "world"); db.StringSet("hello", "world");
var val = db.StringGet("hello"); var val = db.StringGet("hello");
Assert.Equal("world", val); Assert.Equal("world", val);
var msgs = conn.FinishProfiling(profiler.MyContext); var msgs = conn.FinishProfiling(profiler.MyContext);
Assert.Equal(2, msgs.Count()); Assert.Equal(2, msgs.Count());
Assert.Contains(msgs, m => m.Command == "GET"); Assert.Contains(msgs, m => m.Command == "GET");
Assert.Contains(msgs, m => m.Command == "SET"); Assert.Contains(msgs, m => m.Command == "SET");
} }
} }
#if DEBUG #if DEBUG
[Fact] [Fact]
public void MovedProfiling() public void MovedProfiling()
{ {
const string Key = "redirected-key"; const string Key = "redirected-key";
const string Value = "redirected-value"; const string Value = "redirected-value";
var profiler = new TestProfiler(); var profiler = new TestProfiler();
using (var conn = Create()) using (var conn = Create())
{ {
conn.RegisterProfiler(profiler); conn.RegisterProfiler(profiler);
var endpoints = conn.GetEndPoints(); var endpoints = conn.GetEndPoints();
var servers = endpoints.Select(e => conn.GetServer(e)); var servers = endpoints.Select(e => conn.GetServer(e));
conn.BeginProfiling(profiler.MyContext); conn.BeginProfiling(profiler.MyContext);
var db = conn.GetDatabase(); var db = conn.GetDatabase();
db.KeyDelete(Key); db.KeyDelete(Key);
db.StringSet(Key, Value); db.StringSet(Key, Value);
var config = servers.First().ClusterConfiguration; var config = servers.First().ClusterConfiguration;
Assert.NotNull(config); Assert.NotNull(config);
int slot = conn.HashSlot(Key); int slot = conn.HashSlot(Key);
var rightMasterNode = config.GetBySlot(Key); var rightMasterNode = config.GetBySlot(Key);
Assert.NotNull(rightMasterNode); Assert.NotNull(rightMasterNode);
string a = conn.GetServer(rightMasterNode.EndPoint).StringGet(db.Database, Key); string a = conn.GetServer(rightMasterNode.EndPoint).StringGet(db.Database, Key);
Assert.Equal(Value, a); // right master Assert.Equal(Value, a); // right master
var wrongMasterNode = config.Nodes.FirstOrDefault(x => !x.IsSlave && x.NodeId != rightMasterNode.NodeId); var wrongMasterNode = config.Nodes.FirstOrDefault(x => !x.IsSlave && x.NodeId != rightMasterNode.NodeId);
Assert.NotNull(wrongMasterNode); Assert.NotNull(wrongMasterNode);
string b = conn.GetServer(wrongMasterNode.EndPoint).StringGet(db.Database, Key); string b = conn.GetServer(wrongMasterNode.EndPoint).StringGet(db.Database, Key);
Assert.Equal(Value, b); // wrong master, allow redirect Assert.Equal(Value, b); // wrong master, allow redirect
var msgs = conn.FinishProfiling(profiler.MyContext).ToList(); var msgs = conn.FinishProfiling(profiler.MyContext).ToList();
// verify that things actually got recorded properly, and the retransmission profilings are connected as expected // verify that things actually got recorded properly, and the retransmission profilings are connected as expected
{ {
// expect 1 DEL, 1 SET, 1 GET (to right master), 1 GET (to wrong master) that was responded to by an ASK, and 1 GET (to right master or a slave of it) // expect 1 DEL, 1 SET, 1 GET (to right master), 1 GET (to wrong master) that was responded to by an ASK, and 1 GET (to right master or a slave of it)
Assert.Equal(5, msgs.Count); Assert.Equal(5, msgs.Count);
Assert.Equal(1, msgs.Count(c => c.Command == "DEL")); Assert.Equal(1, msgs.Count(c => c.Command == "DEL"));
Assert.Equal(1, msgs.Count(c => c.Command == "SET")); Assert.Equal(1, msgs.Count(c => c.Command == "SET"));
Assert.Equal(3, msgs.Count(c => c.Command == "GET")); Assert.Equal(3, msgs.Count(c => c.Command == "GET"));
var toRightMasterNotRetransmission = msgs.Where(m => m.Command == "GET" && m.EndPoint.Equals(rightMasterNode.EndPoint) && m.RetransmissionOf == null); var toRightMasterNotRetransmission = msgs.Where(m => m.Command == "GET" && m.EndPoint.Equals(rightMasterNode.EndPoint) && m.RetransmissionOf == null);
Assert.Single(toRightMasterNotRetransmission); Assert.Single(toRightMasterNotRetransmission);
var toWrongMasterWithoutRetransmission = msgs.Where(m => m.Command == "GET" && m.EndPoint.Equals(wrongMasterNode.EndPoint) && m.RetransmissionOf == null); var toWrongMasterWithoutRetransmission = msgs.Where(m => m.Command == "GET" && m.EndPoint.Equals(wrongMasterNode.EndPoint) && m.RetransmissionOf == null);
Assert.Single(toWrongMasterWithoutRetransmission); Assert.Single(toWrongMasterWithoutRetransmission);
var toRightMasterOrSlaveAsRetransmission = msgs.Where(m => m.Command == "GET" && (m.EndPoint.Equals(rightMasterNode.EndPoint) || rightMasterNode.Children.Any(c => m.EndPoint.Equals(c.EndPoint))) && m.RetransmissionOf != null); var toRightMasterOrSlaveAsRetransmission = msgs.Where(m => m.Command == "GET" && (m.EndPoint.Equals(rightMasterNode.EndPoint) || rightMasterNode.Children.Any(c => m.EndPoint.Equals(c.EndPoint))) && m.RetransmissionOf != null);
Assert.Single(toRightMasterOrSlaveAsRetransmission); Assert.Single(toRightMasterOrSlaveAsRetransmission);
var originalWrongMaster = toWrongMasterWithoutRetransmission.Single(); var originalWrongMaster = toWrongMasterWithoutRetransmission.Single();
var retransmissionToRight = toRightMasterOrSlaveAsRetransmission.Single(); var retransmissionToRight = toRightMasterOrSlaveAsRetransmission.Single();
Assert.True(object.ReferenceEquals(originalWrongMaster, retransmissionToRight.RetransmissionOf)); Assert.True(object.ReferenceEquals(originalWrongMaster, retransmissionToRight.RetransmissionOf));
} }
foreach (var msg in msgs) foreach (var msg in msgs)
{ {
Assert.True(msg.CommandCreated != default(DateTime)); Assert.True(msg.CommandCreated != default(DateTime));
Assert.True(msg.CreationToEnqueued > TimeSpan.Zero); Assert.True(msg.CreationToEnqueued > TimeSpan.Zero);
Assert.True(msg.EnqueuedToSending > TimeSpan.Zero); Assert.True(msg.EnqueuedToSending > TimeSpan.Zero);
Assert.True(msg.SentToResponse > TimeSpan.Zero); Assert.True(msg.SentToResponse > TimeSpan.Zero);
Assert.True(msg.ResponseToCompletion > TimeSpan.Zero); Assert.True(msg.ResponseToCompletion > TimeSpan.Zero);
Assert.True(msg.ElapsedTime > TimeSpan.Zero); Assert.True(msg.ElapsedTime > TimeSpan.Zero);
if (msg.RetransmissionOf != null) if (msg.RetransmissionOf != null)
{ {
// imprecision of DateTime.UtcNow makes this pretty approximate // imprecision of DateTime.UtcNow makes this pretty approximate
Assert.True(msg.RetransmissionOf.CommandCreated <= msg.CommandCreated); Assert.True(msg.RetransmissionOf.CommandCreated <= msg.CommandCreated);
Assert.Equal(RetransmissionReasonType.Moved, msg.RetransmissionReason.Value); Assert.Equal(RetransmissionReasonType.Moved, msg.RetransmissionReason.Value);
} }
else else
{ {
Assert.False(msg.RetransmissionReason.HasValue); Assert.False(msg.RetransmissionReason.HasValue);
} }
} }
} }
} }
#endif #endif
} }
} }
\ No newline at end of file
using System.Threading; using System.Threading;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
public class ConnectionFailedErrors : TestBase public class ConnectionFailedErrors : TestBase
{ {
public ConnectionFailedErrors(ITestOutputHelper output) : base (output) { } public ConnectionFailedErrors(ITestOutputHelper output) : base (output) { }
[Theory] [Theory]
[InlineData(true)] [InlineData(true)]
[InlineData(false)] [InlineData(false)]
public void SSLCertificateValidationError(bool isCertValidationSucceeded) public void SSLCertificateValidationError(bool isCertValidationSucceeded)
{ {
Skip.IfNoConfig(nameof(TestConfig.Config.AzureCacheServer), TestConfig.Current.AzureCacheServer); Skip.IfNoConfig(nameof(TestConfig.Config.AzureCacheServer), TestConfig.Current.AzureCacheServer);
Skip.IfNoConfig(nameof(TestConfig.Config.AzureCachePassword), TestConfig.Current.AzureCachePassword); Skip.IfNoConfig(nameof(TestConfig.Config.AzureCachePassword), TestConfig.Current.AzureCachePassword);
var options = new ConfigurationOptions(); var options = new ConfigurationOptions();
options.EndPoints.Add(TestConfig.Current.AzureCacheServer); options.EndPoints.Add(TestConfig.Current.AzureCacheServer);
options.Ssl = true; options.Ssl = true;
options.Password = TestConfig.Current.AzureCachePassword; options.Password = TestConfig.Current.AzureCachePassword;
options.CertificateValidation += (sender, cert, chain, errors) => isCertValidationSucceeded; options.CertificateValidation += (sender, cert, chain, errors) => isCertValidationSucceeded;
options.AbortOnConnectFail = false; options.AbortOnConnectFail = false;
using (var connection = ConnectionMultiplexer.Connect(options)) using (var connection = ConnectionMultiplexer.Connect(options))
{ {
connection.ConnectionFailed += (object sender, ConnectionFailedEventArgs e) => connection.ConnectionFailed += (object sender, ConnectionFailedEventArgs e) =>
Assert.Equal(ConnectionFailureType.AuthenticationFailure, e.FailureType); Assert.Equal(ConnectionFailureType.AuthenticationFailure, e.FailureType);
if (!isCertValidationSucceeded) if (!isCertValidationSucceeded)
{ {
//validate that in this case it throws an certificatevalidation exception //validate that in this case it throws an certificatevalidation exception
var ex = Assert.Throws<RedisConnectionException>(() => connection.GetDatabase().Ping()); var ex = Assert.Throws<RedisConnectionException>(() => connection.GetDatabase().Ping());
var rde = (RedisConnectionException)ex.InnerException; var rde = (RedisConnectionException)ex.InnerException;
Assert.Equal(ConnectionFailureType.AuthenticationFailure, rde.FailureType); Assert.Equal(ConnectionFailureType.AuthenticationFailure, rde.FailureType);
Assert.Equal("The remote certificate is invalid according to the validation procedure.", rde.InnerException.Message); Assert.Equal("The remote certificate is invalid according to the validation procedure.", rde.InnerException.Message);
} }
else else
{ {
connection.GetDatabase().Ping(); connection.GetDatabase().Ping();
} }
//wait for a second for connectionfailed event to fire //wait for a second for connectionfailed event to fire
Thread.Sleep(1000); Thread.Sleep(1000);
} }
} }
[Fact] [Fact]
public void AuthenticationFailureError() public void AuthenticationFailureError()
{ {
Skip.IfNoConfig(nameof(TestConfig.Config.AzureCacheServer), TestConfig.Current.AzureCacheServer); Skip.IfNoConfig(nameof(TestConfig.Config.AzureCacheServer), TestConfig.Current.AzureCacheServer);
var options = new ConfigurationOptions(); var options = new ConfigurationOptions();
options.EndPoints.Add(TestConfig.Current.AzureCacheServer); options.EndPoints.Add(TestConfig.Current.AzureCacheServer);
options.Ssl = true; options.Ssl = true;
options.Password = ""; options.Password = "";
options.AbortOnConnectFail = false; options.AbortOnConnectFail = false;
using (var muxer = ConnectionMultiplexer.Connect(options)) using (var muxer = ConnectionMultiplexer.Connect(options))
{ {
muxer.ConnectionFailed += (object sender, ConnectionFailedEventArgs e) => muxer.ConnectionFailed += (object sender, ConnectionFailedEventArgs e) =>
Assert.Equal(ConnectionFailureType.AuthenticationFailure, e.FailureType); Assert.Equal(ConnectionFailureType.AuthenticationFailure, e.FailureType);
var ex = Assert.Throws<RedisConnectionException>(() => muxer.GetDatabase().Ping()); var ex = Assert.Throws<RedisConnectionException>(() => muxer.GetDatabase().Ping());
var rde = (RedisConnectionException)ex.InnerException; var rde = (RedisConnectionException)ex.InnerException;
Assert.Equal(CommandStatus.WaitingToBeSent, ex.CommandStatus); Assert.Equal(CommandStatus.WaitingToBeSent, ex.CommandStatus);
Assert.Equal(ConnectionFailureType.AuthenticationFailure, rde.FailureType); Assert.Equal(ConnectionFailureType.AuthenticationFailure, rde.FailureType);
Assert.Equal("Error: NOAUTH Authentication required. Verify if the Redis password provided is correct.", rde.InnerException.Message); Assert.Equal("Error: NOAUTH Authentication required. Verify if the Redis password provided is correct.", rde.InnerException.Message);
//wait for a second for connectionfailed event to fire //wait for a second for connectionfailed event to fire
Thread.Sleep(1000); Thread.Sleep(1000);
} }
} }
[Fact] [Fact]
public void SocketFailureError() public void SocketFailureError()
{ {
var options = new ConfigurationOptions(); var options = new ConfigurationOptions();
options.EndPoints.Add(".redis.cache.windows.net"); options.EndPoints.Add(".redis.cache.windows.net");
options.Ssl = true; options.Ssl = true;
options.Password = ""; options.Password = "";
options.AbortOnConnectFail = false; options.AbortOnConnectFail = false;
using (var muxer = ConnectionMultiplexer.Connect(options)) using (var muxer = ConnectionMultiplexer.Connect(options))
{ {
var ex = Assert.Throws<RedisConnectionException>(() => muxer.GetDatabase().Ping()); var ex = Assert.Throws<RedisConnectionException>(() => muxer.GetDatabase().Ping());
var rde = (RedisConnectionException)ex.InnerException; var rde = (RedisConnectionException)ex.InnerException;
Assert.Equal(ConnectionFailureType.SocketFailure, rde.FailureType); Assert.Equal(ConnectionFailureType.SocketFailure, rde.FailureType);
} }
} }
#if DEBUG // needs AllowConnect, which is DEBUG only #if DEBUG // needs AllowConnect, which is DEBUG only
[Fact] [Fact]
public void AbortOnConnectFailFalseConnectTimeoutError() public void AbortOnConnectFailFalseConnectTimeoutError()
{ {
Skip.IfNoConfig(nameof(TestConfig.Config.AzureCacheServer), TestConfig.Current.AzureCacheServer); Skip.IfNoConfig(nameof(TestConfig.Config.AzureCacheServer), TestConfig.Current.AzureCacheServer);
Skip.IfNoConfig(nameof(TestConfig.Config.AzureCachePassword), TestConfig.Current.AzureCachePassword); Skip.IfNoConfig(nameof(TestConfig.Config.AzureCachePassword), TestConfig.Current.AzureCachePassword);
var options = new ConfigurationOptions(); var options = new ConfigurationOptions();
options.EndPoints.Add(TestConfig.Current.AzureCacheServer); options.EndPoints.Add(TestConfig.Current.AzureCacheServer);
options.Ssl = true; options.Ssl = true;
options.ConnectTimeout = 0; options.ConnectTimeout = 0;
options.Password = TestConfig.Current.AzureCachePassword; options.Password = TestConfig.Current.AzureCachePassword;
using (var muxer = ConnectionMultiplexer.Connect(options)) using (var muxer = ConnectionMultiplexer.Connect(options))
{ {
var ex = Assert.Throws<RedisConnectionException>(() => muxer.GetDatabase().Ping()); var ex = Assert.Throws<RedisConnectionException>(() => muxer.GetDatabase().Ping());
Assert.Contains("ConnectTimeout", ex.Message); Assert.Contains("ConnectTimeout", ex.Message);
} }
} }
[Fact] [Fact]
public void CheckFailureRecovered() public void CheckFailureRecovered()
{ {
try try
{ {
using (var muxer = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true)) using (var muxer = Create(keepAlive: 1, connectTimeout: 10000, allowAdmin: true))
{ {
var conn = muxer.GetDatabase(); var conn = muxer.GetDatabase();
var server = muxer.GetServer(muxer.GetEndPoints()[0]); var server = muxer.GetServer(muxer.GetEndPoints()[0]);
muxer.AllowConnect = false; muxer.AllowConnect = false;
SocketManager.ConnectCompletionType = CompletionType.Async; SocketManager.ConnectCompletionType = CompletionType.Async;
server.SimulateConnectionFailure(); server.SimulateConnectionFailure();
Assert.Equal(ConnectionFailureType.SocketFailure, ((RedisConnectionException)muxer.GetServerSnapshot()[0].LastException).FailureType); Assert.Equal(ConnectionFailureType.SocketFailure, ((RedisConnectionException)muxer.GetServerSnapshot()[0].LastException).FailureType);
// should reconnect within 1 keepalive interval // should reconnect within 1 keepalive interval
muxer.AllowConnect = true; muxer.AllowConnect = true;
Thread.Sleep(2000); Thread.Sleep(2000);
Assert.Null(muxer.GetServerSnapshot()[0].LastException); Assert.Null(muxer.GetServerSnapshot()[0].LastException);
} }
} }
finally finally
{ {
SocketManager.ConnectCompletionType = CompletionType.Any; SocketManager.ConnectCompletionType = CompletionType.Any;
ClearAmbientFailures(); ClearAmbientFailures();
} }
} }
#endif #endif
[Fact] [Fact]
public void TryGetAzureRoleInstanceIdNoThrow() public void TryGetAzureRoleInstanceIdNoThrow()
{ {
Assert.Null(ConnectionMultiplexer.TryGetAzureRoleInstanceIdNoThrow()); Assert.Null(ConnectionMultiplexer.TryGetAzureRoleInstanceIdNoThrow());
} }
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Net; using System.Net;
using System.Text; using System.Text;
using Moq; using Moq;
using StackExchange.Redis.KeyspaceIsolation; using StackExchange.Redis.KeyspaceIsolation;
using Xunit; using Xunit;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
public sealed class DatabaseWrapperTests public sealed class DatabaseWrapperTests
{ {
private readonly Mock<IDatabase> mock; private readonly Mock<IDatabase> mock;
private readonly DatabaseWrapper wrapper; private readonly DatabaseWrapper wrapper;
public DatabaseWrapperTests() public DatabaseWrapperTests()
{ {
mock = new Mock<IDatabase>(); mock = new Mock<IDatabase>();
wrapper = new DatabaseWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:")); wrapper = new DatabaseWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
} }
[Fact] [Fact]
public void CreateBatch() public void CreateBatch()
{ {
object asyncState = new object(); object asyncState = new object();
IBatch innerBatch = new Mock<IBatch>().Object; IBatch innerBatch = new Mock<IBatch>().Object;
mock.Setup(_ => _.CreateBatch(asyncState)).Returns(innerBatch); mock.Setup(_ => _.CreateBatch(asyncState)).Returns(innerBatch);
IBatch wrappedBatch = wrapper.CreateBatch(asyncState); IBatch wrappedBatch = wrapper.CreateBatch(asyncState);
mock.Verify(_ => _.CreateBatch(asyncState)); mock.Verify(_ => _.CreateBatch(asyncState));
Assert.IsType<BatchWrapper>(wrappedBatch); Assert.IsType<BatchWrapper>(wrappedBatch);
Assert.Same(innerBatch, ((BatchWrapper)wrappedBatch).Inner); Assert.Same(innerBatch, ((BatchWrapper)wrappedBatch).Inner);
} }
[Fact] [Fact]
public void CreateTransaction() public void CreateTransaction()
{ {
object asyncState = new object(); object asyncState = new object();
ITransaction innerTransaction = new Mock<ITransaction>().Object; ITransaction innerTransaction = new Mock<ITransaction>().Object;
mock.Setup(_ => _.CreateTransaction(asyncState)).Returns(innerTransaction); mock.Setup(_ => _.CreateTransaction(asyncState)).Returns(innerTransaction);
ITransaction wrappedTransaction = wrapper.CreateTransaction(asyncState); ITransaction wrappedTransaction = wrapper.CreateTransaction(asyncState);
mock.Verify(_ => _.CreateTransaction(asyncState)); mock.Verify(_ => _.CreateTransaction(asyncState));
Assert.IsType<TransactionWrapper>(wrappedTransaction); Assert.IsType<TransactionWrapper>(wrappedTransaction);
Assert.Same(innerTransaction, ((TransactionWrapper)wrappedTransaction).Inner); Assert.Same(innerTransaction, ((TransactionWrapper)wrappedTransaction).Inner);
} }
[Fact] [Fact]
public void DebugObject() public void DebugObject()
{ {
wrapper.DebugObject("key", CommandFlags.HighPriority); wrapper.DebugObject("key", CommandFlags.HighPriority);
mock.Verify(_ => _.DebugObject("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.DebugObject("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void Get_Database() public void Get_Database()
{ {
mock.SetupGet(_ => _.Database).Returns(123); mock.SetupGet(_ => _.Database).Returns(123);
Assert.Equal(123, wrapper.Database); Assert.Equal(123, wrapper.Database);
} }
[Fact] [Fact]
public void HashDecrement_1() public void HashDecrement_1()
{ {
wrapper.HashDecrement("key", "hashField", 123, CommandFlags.HighPriority); wrapper.HashDecrement("key", "hashField", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.HashDecrement("prefix:key", "hashField", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.HashDecrement("prefix:key", "hashField", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashDecrement_2() public void HashDecrement_2()
{ {
wrapper.HashDecrement("key", "hashField", 1.23, CommandFlags.HighPriority); wrapper.HashDecrement("key", "hashField", 1.23, CommandFlags.HighPriority);
mock.Verify(_ => _.HashDecrement("prefix:key", "hashField", 1.23, CommandFlags.HighPriority)); mock.Verify(_ => _.HashDecrement("prefix:key", "hashField", 1.23, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashDelete_1() public void HashDelete_1()
{ {
wrapper.HashDelete("key", "hashField", CommandFlags.HighPriority); wrapper.HashDelete("key", "hashField", CommandFlags.HighPriority);
mock.Verify(_ => _.HashDelete("prefix:key", "hashField", CommandFlags.HighPriority)); mock.Verify(_ => _.HashDelete("prefix:key", "hashField", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashDelete_2() public void HashDelete_2()
{ {
RedisValue[] hashFields = new RedisValue[0]; RedisValue[] hashFields = new RedisValue[0];
wrapper.HashDelete("key", hashFields, CommandFlags.HighPriority); wrapper.HashDelete("key", hashFields, CommandFlags.HighPriority);
mock.Verify(_ => _.HashDelete("prefix:key", hashFields, CommandFlags.HighPriority)); mock.Verify(_ => _.HashDelete("prefix:key", hashFields, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashExists() public void HashExists()
{ {
wrapper.HashExists("key", "hashField", CommandFlags.HighPriority); wrapper.HashExists("key", "hashField", CommandFlags.HighPriority);
mock.Verify(_ => _.HashExists("prefix:key", "hashField", CommandFlags.HighPriority)); mock.Verify(_ => _.HashExists("prefix:key", "hashField", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashGet_1() public void HashGet_1()
{ {
wrapper.HashGet("key", "hashField", CommandFlags.HighPriority); wrapper.HashGet("key", "hashField", CommandFlags.HighPriority);
mock.Verify(_ => _.HashGet("prefix:key", "hashField", CommandFlags.HighPriority)); mock.Verify(_ => _.HashGet("prefix:key", "hashField", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashGet_2() public void HashGet_2()
{ {
RedisValue[] hashFields = new RedisValue[0]; RedisValue[] hashFields = new RedisValue[0];
wrapper.HashGet("key", hashFields, CommandFlags.HighPriority); wrapper.HashGet("key", hashFields, CommandFlags.HighPriority);
mock.Verify(_ => _.HashGet("prefix:key", hashFields, CommandFlags.HighPriority)); mock.Verify(_ => _.HashGet("prefix:key", hashFields, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashGetAll() public void HashGetAll()
{ {
wrapper.HashGetAll("key", CommandFlags.HighPriority); wrapper.HashGetAll("key", CommandFlags.HighPriority);
mock.Verify(_ => _.HashGetAll("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.HashGetAll("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashIncrement_1() public void HashIncrement_1()
{ {
wrapper.HashIncrement("key", "hashField", 123, CommandFlags.HighPriority); wrapper.HashIncrement("key", "hashField", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.HashIncrement("prefix:key", "hashField", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.HashIncrement("prefix:key", "hashField", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashIncrement_2() public void HashIncrement_2()
{ {
wrapper.HashIncrement("key", "hashField", 1.23, CommandFlags.HighPriority); wrapper.HashIncrement("key", "hashField", 1.23, CommandFlags.HighPriority);
mock.Verify(_ => _.HashIncrement("prefix:key", "hashField", 1.23, CommandFlags.HighPriority)); mock.Verify(_ => _.HashIncrement("prefix:key", "hashField", 1.23, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashKeys() public void HashKeys()
{ {
wrapper.HashKeys("key", CommandFlags.HighPriority); wrapper.HashKeys("key", CommandFlags.HighPriority);
mock.Verify(_ => _.HashKeys("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.HashKeys("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashLength() public void HashLength()
{ {
wrapper.HashLength("key", CommandFlags.HighPriority); wrapper.HashLength("key", CommandFlags.HighPriority);
mock.Verify(_ => _.HashLength("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.HashLength("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashScan() public void HashScan()
{ {
wrapper.HashScan("key", "pattern", 123, flags: CommandFlags.HighPriority); wrapper.HashScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.HashScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority)); mock.Verify(_ => _.HashScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashSet_1() public void HashSet_1()
{ {
HashEntry[] hashFields = new HashEntry[0]; HashEntry[] hashFields = new HashEntry[0];
wrapper.HashSet("key", hashFields, CommandFlags.HighPriority); wrapper.HashSet("key", hashFields, CommandFlags.HighPriority);
mock.Verify(_ => _.HashSet("prefix:key", hashFields, CommandFlags.HighPriority)); mock.Verify(_ => _.HashSet("prefix:key", hashFields, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashSet_2() public void HashSet_2()
{ {
wrapper.HashSet("key", "hashField", "value", When.Exists, CommandFlags.HighPriority); wrapper.HashSet("key", "hashField", "value", When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.HashSet("prefix:key", "hashField", "value", When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.HashSet("prefix:key", "hashField", "value", When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashValues() public void HashValues()
{ {
wrapper.HashValues("key", CommandFlags.HighPriority); wrapper.HashValues("key", CommandFlags.HighPriority);
mock.Verify(_ => _.HashValues("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.HashValues("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HyperLogLogAdd_1() public void HyperLogLogAdd_1()
{ {
wrapper.HyperLogLogAdd("key", "value", CommandFlags.HighPriority); wrapper.HyperLogLogAdd("key", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.HyperLogLogAdd("prefix:key", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.HyperLogLogAdd("prefix:key", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HyperLogLogAdd_2() public void HyperLogLogAdd_2()
{ {
RedisValue[] values = new RedisValue[0]; RedisValue[] values = new RedisValue[0];
wrapper.HyperLogLogAdd("key", values, CommandFlags.HighPriority); wrapper.HyperLogLogAdd("key", values, CommandFlags.HighPriority);
mock.Verify(_ => _.HyperLogLogAdd("prefix:key", values, CommandFlags.HighPriority)); mock.Verify(_ => _.HyperLogLogAdd("prefix:key", values, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HyperLogLogLength() public void HyperLogLogLength()
{ {
wrapper.HyperLogLogLength("key", CommandFlags.HighPriority); wrapper.HyperLogLogLength("key", CommandFlags.HighPriority);
mock.Verify(_ => _.HyperLogLogLength("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.HyperLogLogLength("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HyperLogLogMerge_1() public void HyperLogLogMerge_1()
{ {
wrapper.HyperLogLogMerge("destination", "first", "second", CommandFlags.HighPriority); wrapper.HyperLogLogMerge("destination", "first", "second", CommandFlags.HighPriority);
mock.Verify(_ => _.HyperLogLogMerge("prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority)); mock.Verify(_ => _.HyperLogLogMerge("prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HyperLogLogMerge_2() public void HyperLogLogMerge_2()
{ {
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.HyperLogLogMerge("destination", keys, CommandFlags.HighPriority); wrapper.HyperLogLogMerge("destination", keys, CommandFlags.HighPriority);
mock.Verify(_ => _.HyperLogLogMerge("prefix:destination", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.HyperLogLogMerge("prefix:destination", It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void IdentifyEndpoint() public void IdentifyEndpoint()
{ {
wrapper.IdentifyEndpoint("key", CommandFlags.HighPriority); wrapper.IdentifyEndpoint("key", CommandFlags.HighPriority);
mock.Verify(_ => _.IdentifyEndpoint("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.IdentifyEndpoint("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyDelete_1() public void KeyDelete_1()
{ {
wrapper.KeyDelete("key", CommandFlags.HighPriority); wrapper.KeyDelete("key", CommandFlags.HighPriority);
mock.Verify(_ => _.KeyDelete("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.KeyDelete("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyDelete_2() public void KeyDelete_2()
{ {
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.KeyDelete(keys, CommandFlags.HighPriority); wrapper.KeyDelete(keys, CommandFlags.HighPriority);
mock.Verify(_ => _.KeyDelete(It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.KeyDelete(It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyDump() public void KeyDump()
{ {
wrapper.KeyDump("key", CommandFlags.HighPriority); wrapper.KeyDump("key", CommandFlags.HighPriority);
mock.Verify(_ => _.KeyDump("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.KeyDump("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyExists() public void KeyExists()
{ {
wrapper.KeyExists("key", CommandFlags.HighPriority); wrapper.KeyExists("key", CommandFlags.HighPriority);
mock.Verify(_ => _.KeyExists("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.KeyExists("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyExpire_1() public void KeyExpire_1()
{ {
TimeSpan expiry = TimeSpan.FromSeconds(123); TimeSpan expiry = TimeSpan.FromSeconds(123);
wrapper.KeyExpire("key", expiry, CommandFlags.HighPriority); wrapper.KeyExpire("key", expiry, CommandFlags.HighPriority);
mock.Verify(_ => _.KeyExpire("prefix:key", expiry, CommandFlags.HighPriority)); mock.Verify(_ => _.KeyExpire("prefix:key", expiry, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyExpire_2() public void KeyExpire_2()
{ {
DateTime expiry = DateTime.Now; DateTime expiry = DateTime.Now;
wrapper.KeyExpire("key", expiry, CommandFlags.HighPriority); wrapper.KeyExpire("key", expiry, CommandFlags.HighPriority);
mock.Verify(_ => _.KeyExpire("prefix:key", expiry, CommandFlags.HighPriority)); mock.Verify(_ => _.KeyExpire("prefix:key", expiry, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyMigrate() public void KeyMigrate()
{ {
EndPoint toServer = new IPEndPoint(IPAddress.Loopback, 123); EndPoint toServer = new IPEndPoint(IPAddress.Loopback, 123);
wrapper.KeyMigrate("key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.HighPriority); wrapper.KeyMigrate("key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.HighPriority);
mock.Verify(_ => _.KeyMigrate("prefix:key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.HighPriority)); mock.Verify(_ => _.KeyMigrate("prefix:key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyMove() public void KeyMove()
{ {
wrapper.KeyMove("key", 123, CommandFlags.HighPriority); wrapper.KeyMove("key", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.KeyMove("prefix:key", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.KeyMove("prefix:key", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyPersist() public void KeyPersist()
{ {
wrapper.KeyPersist("key", CommandFlags.HighPriority); wrapper.KeyPersist("key", CommandFlags.HighPriority);
mock.Verify(_ => _.KeyPersist("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.KeyPersist("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyRandom() public void KeyRandom()
{ {
Assert.Throws<NotSupportedException>(() => wrapper.KeyRandom()); Assert.Throws<NotSupportedException>(() => wrapper.KeyRandom());
} }
[Fact] [Fact]
public void KeyRename() public void KeyRename()
{ {
wrapper.KeyRename("key", "newKey", When.Exists, CommandFlags.HighPriority); wrapper.KeyRename("key", "newKey", When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.KeyRename("prefix:key", "prefix:newKey", When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.KeyRename("prefix:key", "prefix:newKey", When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyRestore() public void KeyRestore()
{ {
Byte[] value = new Byte[0]; Byte[] value = new Byte[0];
TimeSpan expiry = TimeSpan.FromSeconds(123); TimeSpan expiry = TimeSpan.FromSeconds(123);
wrapper.KeyRestore("key", value, expiry, CommandFlags.HighPriority); wrapper.KeyRestore("key", value, expiry, CommandFlags.HighPriority);
mock.Verify(_ => _.KeyRestore("prefix:key", value, expiry, CommandFlags.HighPriority)); mock.Verify(_ => _.KeyRestore("prefix:key", value, expiry, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyTimeToLive() public void KeyTimeToLive()
{ {
wrapper.KeyTimeToLive("key", CommandFlags.HighPriority); wrapper.KeyTimeToLive("key", CommandFlags.HighPriority);
mock.Verify(_ => _.KeyTimeToLive("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.KeyTimeToLive("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyType() public void KeyType()
{ {
wrapper.KeyType("key", CommandFlags.HighPriority); wrapper.KeyType("key", CommandFlags.HighPriority);
mock.Verify(_ => _.KeyType("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.KeyType("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListGetByIndex() public void ListGetByIndex()
{ {
wrapper.ListGetByIndex("key", 123, CommandFlags.HighPriority); wrapper.ListGetByIndex("key", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.ListGetByIndex("prefix:key", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.ListGetByIndex("prefix:key", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListInsertAfter() public void ListInsertAfter()
{ {
wrapper.ListInsertAfter("key", "pivot", "value", CommandFlags.HighPriority); wrapper.ListInsertAfter("key", "pivot", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.ListInsertAfter("prefix:key", "pivot", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.ListInsertAfter("prefix:key", "pivot", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListInsertBefore() public void ListInsertBefore()
{ {
wrapper.ListInsertBefore("key", "pivot", "value", CommandFlags.HighPriority); wrapper.ListInsertBefore("key", "pivot", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.ListInsertBefore("prefix:key", "pivot", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.ListInsertBefore("prefix:key", "pivot", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListLeftPop() public void ListLeftPop()
{ {
wrapper.ListLeftPop("key", CommandFlags.HighPriority); wrapper.ListLeftPop("key", CommandFlags.HighPriority);
mock.Verify(_ => _.ListLeftPop("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.ListLeftPop("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListLeftPush_1() public void ListLeftPush_1()
{ {
wrapper.ListLeftPush("key", "value", When.Exists, CommandFlags.HighPriority); wrapper.ListLeftPush("key", "value", When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.ListLeftPush("prefix:key", "value", When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.ListLeftPush("prefix:key", "value", When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListLeftPush_2() public void ListLeftPush_2()
{ {
RedisValue[] values = new RedisValue[0]; RedisValue[] values = new RedisValue[0];
wrapper.ListLeftPush("key", values, CommandFlags.HighPriority); wrapper.ListLeftPush("key", values, CommandFlags.HighPriority);
mock.Verify(_ => _.ListLeftPush("prefix:key", values, CommandFlags.HighPriority)); mock.Verify(_ => _.ListLeftPush("prefix:key", values, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListLength() public void ListLength()
{ {
wrapper.ListLength("key", CommandFlags.HighPriority); wrapper.ListLength("key", CommandFlags.HighPriority);
mock.Verify(_ => _.ListLength("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.ListLength("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListRange() public void ListRange()
{ {
wrapper.ListRange("key", 123, 456, CommandFlags.HighPriority); wrapper.ListRange("key", 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.ListRange("prefix:key", 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.ListRange("prefix:key", 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListRemove() public void ListRemove()
{ {
wrapper.ListRemove("key", "value", 123, CommandFlags.HighPriority); wrapper.ListRemove("key", "value", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.ListRemove("prefix:key", "value", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.ListRemove("prefix:key", "value", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListRightPop() public void ListRightPop()
{ {
wrapper.ListRightPop("key", CommandFlags.HighPriority); wrapper.ListRightPop("key", CommandFlags.HighPriority);
mock.Verify(_ => _.ListRightPop("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.ListRightPop("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListRightPopLeftPush() public void ListRightPopLeftPush()
{ {
wrapper.ListRightPopLeftPush("source", "destination", CommandFlags.HighPriority); wrapper.ListRightPopLeftPush("source", "destination", CommandFlags.HighPriority);
mock.Verify(_ => _.ListRightPopLeftPush("prefix:source", "prefix:destination", CommandFlags.HighPriority)); mock.Verify(_ => _.ListRightPopLeftPush("prefix:source", "prefix:destination", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListRightPush_1() public void ListRightPush_1()
{ {
wrapper.ListRightPush("key", "value", When.Exists, CommandFlags.HighPriority); wrapper.ListRightPush("key", "value", When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.ListRightPush("prefix:key", "value", When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.ListRightPush("prefix:key", "value", When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListRightPush_2() public void ListRightPush_2()
{ {
RedisValue[] values = new RedisValue[0]; RedisValue[] values = new RedisValue[0];
wrapper.ListRightPush("key", values, CommandFlags.HighPriority); wrapper.ListRightPush("key", values, CommandFlags.HighPriority);
mock.Verify(_ => _.ListRightPush("prefix:key", values, CommandFlags.HighPriority)); mock.Verify(_ => _.ListRightPush("prefix:key", values, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListSetByIndex() public void ListSetByIndex()
{ {
wrapper.ListSetByIndex("key", 123, "value", CommandFlags.HighPriority); wrapper.ListSetByIndex("key", 123, "value", CommandFlags.HighPriority);
mock.Verify(_ => _.ListSetByIndex("prefix:key", 123, "value", CommandFlags.HighPriority)); mock.Verify(_ => _.ListSetByIndex("prefix:key", 123, "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListTrim() public void ListTrim()
{ {
wrapper.ListTrim("key", 123, 456, CommandFlags.HighPriority); wrapper.ListTrim("key", 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.ListTrim("prefix:key", 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.ListTrim("prefix:key", 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void LockExtend() public void LockExtend()
{ {
TimeSpan expiry = TimeSpan.FromSeconds(123); TimeSpan expiry = TimeSpan.FromSeconds(123);
wrapper.LockExtend("key", "value", expiry, CommandFlags.HighPriority); wrapper.LockExtend("key", "value", expiry, CommandFlags.HighPriority);
mock.Verify(_ => _.LockExtend("prefix:key", "value", expiry, CommandFlags.HighPriority)); mock.Verify(_ => _.LockExtend("prefix:key", "value", expiry, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void LockQuery() public void LockQuery()
{ {
wrapper.LockQuery("key", CommandFlags.HighPriority); wrapper.LockQuery("key", CommandFlags.HighPriority);
mock.Verify(_ => _.LockQuery("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.LockQuery("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void LockRelease() public void LockRelease()
{ {
wrapper.LockRelease("key", "value", CommandFlags.HighPriority); wrapper.LockRelease("key", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.LockRelease("prefix:key", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.LockRelease("prefix:key", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void LockTake() public void LockTake()
{ {
TimeSpan expiry = TimeSpan.FromSeconds(123); TimeSpan expiry = TimeSpan.FromSeconds(123);
wrapper.LockTake("key", "value", expiry, CommandFlags.HighPriority); wrapper.LockTake("key", "value", expiry, CommandFlags.HighPriority);
mock.Verify(_ => _.LockTake("prefix:key", "value", expiry, CommandFlags.HighPriority)); mock.Verify(_ => _.LockTake("prefix:key", "value", expiry, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void Publish() public void Publish()
{ {
wrapper.Publish("channel", "message", CommandFlags.HighPriority); wrapper.Publish("channel", "message", CommandFlags.HighPriority);
mock.Verify(_ => _.Publish("prefix:channel", "message", CommandFlags.HighPriority)); mock.Verify(_ => _.Publish("prefix:channel", "message", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ScriptEvaluate_1() public void ScriptEvaluate_1()
{ {
byte[] hash = new byte[0]; byte[] hash = new byte[0];
RedisValue[] values = new RedisValue[0]; RedisValue[] values = new RedisValue[0];
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.ScriptEvaluate(hash, keys, values, CommandFlags.HighPriority); wrapper.ScriptEvaluate(hash, keys, values, CommandFlags.HighPriority);
mock.Verify(_ => _.ScriptEvaluate(hash, It.Is(valid), values, CommandFlags.HighPriority)); mock.Verify(_ => _.ScriptEvaluate(hash, It.Is(valid), values, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ScriptEvaluate_2() public void ScriptEvaluate_2()
{ {
RedisValue[] values = new RedisValue[0]; RedisValue[] values = new RedisValue[0];
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.ScriptEvaluate("script", keys, values, CommandFlags.HighPriority); wrapper.ScriptEvaluate("script", keys, values, CommandFlags.HighPriority);
mock.Verify(_ => _.ScriptEvaluate("script", It.Is(valid), values, CommandFlags.HighPriority)); mock.Verify(_ => _.ScriptEvaluate("script", It.Is(valid), values, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetAdd_1() public void SetAdd_1()
{ {
wrapper.SetAdd("key", "value", CommandFlags.HighPriority); wrapper.SetAdd("key", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.SetAdd("prefix:key", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.SetAdd("prefix:key", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetAdd_2() public void SetAdd_2()
{ {
RedisValue[] values = new RedisValue[0]; RedisValue[] values = new RedisValue[0];
wrapper.SetAdd("key", values, CommandFlags.HighPriority); wrapper.SetAdd("key", values, CommandFlags.HighPriority);
mock.Verify(_ => _.SetAdd("prefix:key", values, CommandFlags.HighPriority)); mock.Verify(_ => _.SetAdd("prefix:key", values, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetCombine_1() public void SetCombine_1()
{ {
wrapper.SetCombine(SetOperation.Intersect, "first", "second", CommandFlags.HighPriority); wrapper.SetCombine(SetOperation.Intersect, "first", "second", CommandFlags.HighPriority);
mock.Verify(_ => _.SetCombine(SetOperation.Intersect, "prefix:first", "prefix:second", CommandFlags.HighPriority)); mock.Verify(_ => _.SetCombine(SetOperation.Intersect, "prefix:first", "prefix:second", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetCombine_2() public void SetCombine_2()
{ {
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.SetCombine(SetOperation.Intersect, keys, CommandFlags.HighPriority); wrapper.SetCombine(SetOperation.Intersect, keys, CommandFlags.HighPriority);
mock.Verify(_ => _.SetCombine(SetOperation.Intersect, It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.SetCombine(SetOperation.Intersect, It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetCombineAndStore_1() public void SetCombineAndStore_1()
{ {
wrapper.SetCombineAndStore(SetOperation.Intersect, "destination", "first", "second", CommandFlags.HighPriority); wrapper.SetCombineAndStore(SetOperation.Intersect, "destination", "first", "second", CommandFlags.HighPriority);
mock.Verify(_ => _.SetCombineAndStore(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority)); mock.Verify(_ => _.SetCombineAndStore(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetCombineAndStore_2() public void SetCombineAndStore_2()
{ {
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.SetCombineAndStore(SetOperation.Intersect, "destination", keys, CommandFlags.HighPriority); wrapper.SetCombineAndStore(SetOperation.Intersect, "destination", keys, CommandFlags.HighPriority);
mock.Verify(_ => _.SetCombineAndStore(SetOperation.Intersect, "prefix:destination", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.SetCombineAndStore(SetOperation.Intersect, "prefix:destination", It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetContains() public void SetContains()
{ {
wrapper.SetContains("key", "value", CommandFlags.HighPriority); wrapper.SetContains("key", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.SetContains("prefix:key", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.SetContains("prefix:key", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetLength() public void SetLength()
{ {
wrapper.SetLength("key", CommandFlags.HighPriority); wrapper.SetLength("key", CommandFlags.HighPriority);
mock.Verify(_ => _.SetLength("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.SetLength("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetMembers() public void SetMembers()
{ {
wrapper.SetMembers("key", CommandFlags.HighPriority); wrapper.SetMembers("key", CommandFlags.HighPriority);
mock.Verify(_ => _.SetMembers("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.SetMembers("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetMove() public void SetMove()
{ {
wrapper.SetMove("source", "destination", "value", CommandFlags.HighPriority); wrapper.SetMove("source", "destination", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.SetMove("prefix:source", "prefix:destination", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.SetMove("prefix:source", "prefix:destination", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetPop() public void SetPop()
{ {
wrapper.SetPop("key", CommandFlags.HighPriority); wrapper.SetPop("key", CommandFlags.HighPriority);
mock.Verify(_ => _.SetPop("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.SetPop("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetRandomMember() public void SetRandomMember()
{ {
wrapper.SetRandomMember("key", CommandFlags.HighPriority); wrapper.SetRandomMember("key", CommandFlags.HighPriority);
mock.Verify(_ => _.SetRandomMember("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.SetRandomMember("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetRandomMembers() public void SetRandomMembers()
{ {
wrapper.SetRandomMembers("key", 123, CommandFlags.HighPriority); wrapper.SetRandomMembers("key", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.SetRandomMembers("prefix:key", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.SetRandomMembers("prefix:key", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetRemove_1() public void SetRemove_1()
{ {
wrapper.SetRemove("key", "value", CommandFlags.HighPriority); wrapper.SetRemove("key", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.SetRemove("prefix:key", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.SetRemove("prefix:key", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetRemove_2() public void SetRemove_2()
{ {
RedisValue[] values = new RedisValue[0]; RedisValue[] values = new RedisValue[0];
wrapper.SetRemove("key", values, CommandFlags.HighPriority); wrapper.SetRemove("key", values, CommandFlags.HighPriority);
mock.Verify(_ => _.SetRemove("prefix:key", values, CommandFlags.HighPriority)); mock.Verify(_ => _.SetRemove("prefix:key", values, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetScan() public void SetScan()
{ {
wrapper.SetScan("key", "pattern", 123, flags: CommandFlags.HighPriority); wrapper.SetScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.SetScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority)); mock.Verify(_ => _.SetScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void Sort() public void Sort()
{ {
RedisValue[] get = new RedisValue[] { "a", "#" }; RedisValue[] get = new RedisValue[] { "a", "#" };
Expression<Func<RedisValue[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#"; Expression<Func<RedisValue[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#";
wrapper.Sort("key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.HighPriority); wrapper.Sort("key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.HighPriority);
wrapper.Sort("key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.HighPriority); wrapper.Sort("key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.HighPriority);
mock.Verify(_ => _.Sort("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.Sort("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", It.Is(valid), CommandFlags.HighPriority));
mock.Verify(_ => _.Sort("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.Sort("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortAndStore() public void SortAndStore()
{ {
RedisValue[] get = new RedisValue[] { "a", "#" }; RedisValue[] get = new RedisValue[] { "a", "#" };
Expression<Func<RedisValue[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#"; Expression<Func<RedisValue[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#";
wrapper.SortAndStore("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.HighPriority); wrapper.SortAndStore("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.HighPriority);
wrapper.SortAndStore("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.HighPriority); wrapper.SortAndStore("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.HighPriority);
mock.Verify(_ => _.SortAndStore("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.SortAndStore("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", It.Is(valid), CommandFlags.HighPriority));
mock.Verify(_ => _.SortAndStore("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.SortAndStore("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetAdd_1() public void SortedSetAdd_1()
{ {
wrapper.SortedSetAdd("key", "member", 1.23, When.Exists, CommandFlags.HighPriority); wrapper.SortedSetAdd("key", "member", 1.23, When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetAdd("prefix:key", "member", 1.23, When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetAdd("prefix:key", "member", 1.23, When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetAdd_2() public void SortedSetAdd_2()
{ {
SortedSetEntry[] values = new SortedSetEntry[0]; SortedSetEntry[] values = new SortedSetEntry[0];
wrapper.SortedSetAdd("key", values, When.Exists, CommandFlags.HighPriority); wrapper.SortedSetAdd("key", values, When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetAdd("prefix:key", values, When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetAdd("prefix:key", values, When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetCombineAndStore_1() public void SortedSetCombineAndStore_1()
{ {
wrapper.SortedSetCombineAndStore(SetOperation.Intersect, "destination", "first", "second", Aggregate.Max, CommandFlags.HighPriority); wrapper.SortedSetCombineAndStore(SetOperation.Intersect, "destination", "first", "second", Aggregate.Max, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetCombineAndStore(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", Aggregate.Max, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetCombineAndStore(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", Aggregate.Max, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetCombineAndStore_2() public void SortedSetCombineAndStore_2()
{ {
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.SetCombineAndStore(SetOperation.Intersect, "destination", keys, CommandFlags.HighPriority); wrapper.SetCombineAndStore(SetOperation.Intersect, "destination", keys, CommandFlags.HighPriority);
mock.Verify(_ => _.SetCombineAndStore(SetOperation.Intersect, "prefix:destination", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.SetCombineAndStore(SetOperation.Intersect, "prefix:destination", It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetDecrement() public void SortedSetDecrement()
{ {
wrapper.SortedSetDecrement("key", "member", 1.23, CommandFlags.HighPriority); wrapper.SortedSetDecrement("key", "member", 1.23, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetDecrement("prefix:key", "member", 1.23, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetDecrement("prefix:key", "member", 1.23, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetIncrement() public void SortedSetIncrement()
{ {
wrapper.SortedSetIncrement("key", "member", 1.23, CommandFlags.HighPriority); wrapper.SortedSetIncrement("key", "member", 1.23, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetIncrement("prefix:key", "member", 1.23, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetIncrement("prefix:key", "member", 1.23, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetLength() public void SortedSetLength()
{ {
wrapper.SortedSetLength("key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority); wrapper.SortedSetLength("key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetLength("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetLength("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetLengthByValue() public void SortedSetLengthByValue()
{ {
wrapper.SortedSetLengthByValue("key", "min", "max", Exclude.Start, CommandFlags.HighPriority); wrapper.SortedSetLengthByValue("key", "min", "max", Exclude.Start, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetLengthByValue("prefix:key", "min", "max", Exclude.Start, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetLengthByValue("prefix:key", "min", "max", Exclude.Start, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRangeByRank() public void SortedSetRangeByRank()
{ {
wrapper.SortedSetRangeByRank("key", 123, 456, Order.Descending, CommandFlags.HighPriority); wrapper.SortedSetRangeByRank("key", 123, 456, Order.Descending, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRangeByRank("prefix:key", 123, 456, Order.Descending, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRangeByRank("prefix:key", 123, 456, Order.Descending, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRangeByRankWithScores() public void SortedSetRangeByRankWithScores()
{ {
wrapper.SortedSetRangeByRankWithScores("key", 123, 456, Order.Descending, CommandFlags.HighPriority); wrapper.SortedSetRangeByRankWithScores("key", 123, 456, Order.Descending, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRangeByRankWithScores("prefix:key", 123, 456, Order.Descending, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRangeByRankWithScores("prefix:key", 123, 456, Order.Descending, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRangeByScore() public void SortedSetRangeByScore()
{ {
wrapper.SortedSetRangeByScore("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority); wrapper.SortedSetRangeByScore("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRangeByScore("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRangeByScore("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRangeByScoreWithScores() public void SortedSetRangeByScoreWithScores()
{ {
wrapper.SortedSetRangeByScoreWithScores("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority); wrapper.SortedSetRangeByScoreWithScores("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRangeByScoreWithScores("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRangeByScoreWithScores("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRangeByValue() public void SortedSetRangeByValue()
{ {
wrapper.SortedSetRangeByValue("key", "min", "max", Exclude.Start, 123, 456, CommandFlags.HighPriority); wrapper.SortedSetRangeByValue("key", "min", "max", Exclude.Start, 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRangeByValue("prefix:key", "min", "max", Exclude.Start, 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRangeByValue("prefix:key", "min", "max", Exclude.Start, 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRank() public void SortedSetRank()
{ {
wrapper.SortedSetRank("key", "member", Order.Descending, CommandFlags.HighPriority); wrapper.SortedSetRank("key", "member", Order.Descending, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRank("prefix:key", "member", Order.Descending, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRank("prefix:key", "member", Order.Descending, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRemove_1() public void SortedSetRemove_1()
{ {
wrapper.SortedSetRemove("key", "member", CommandFlags.HighPriority); wrapper.SortedSetRemove("key", "member", CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRemove("prefix:key", "member", CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRemove("prefix:key", "member", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRemove_2() public void SortedSetRemove_2()
{ {
RedisValue[] members = new RedisValue[0]; RedisValue[] members = new RedisValue[0];
wrapper.SortedSetRemove("key", members, CommandFlags.HighPriority); wrapper.SortedSetRemove("key", members, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRemove("prefix:key", members, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRemove("prefix:key", members, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRemoveRangeByRank() public void SortedSetRemoveRangeByRank()
{ {
wrapper.SortedSetRemoveRangeByRank("key", 123, 456, CommandFlags.HighPriority); wrapper.SortedSetRemoveRangeByRank("key", 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRemoveRangeByRank("prefix:key", 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRemoveRangeByRank("prefix:key", 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRemoveRangeByScore() public void SortedSetRemoveRangeByScore()
{ {
wrapper.SortedSetRemoveRangeByScore("key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority); wrapper.SortedSetRemoveRangeByScore("key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRemoveRangeByScore("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRemoveRangeByScore("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRemoveRangeByValue() public void SortedSetRemoveRangeByValue()
{ {
wrapper.SortedSetRemoveRangeByValue("key", "min", "max", Exclude.Start, CommandFlags.HighPriority); wrapper.SortedSetRemoveRangeByValue("key", "min", "max", Exclude.Start, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRemoveRangeByValue("prefix:key", "min", "max", Exclude.Start, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRemoveRangeByValue("prefix:key", "min", "max", Exclude.Start, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetScan() public void SortedSetScan()
{ {
wrapper.SortedSetScan("key", "pattern", 123, flags: CommandFlags.HighPriority); wrapper.SortedSetScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetScore() public void SortedSetScore()
{ {
wrapper.SortedSetScore("key", "member", CommandFlags.HighPriority); wrapper.SortedSetScore("key", "member", CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetScore("prefix:key", "member", CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetScore("prefix:key", "member", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringAppend() public void StringAppend()
{ {
wrapper.StringAppend("key", "value", CommandFlags.HighPriority); wrapper.StringAppend("key", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.StringAppend("prefix:key", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.StringAppend("prefix:key", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringBitCount() public void StringBitCount()
{ {
wrapper.StringBitCount("key", 123, 456, CommandFlags.HighPriority); wrapper.StringBitCount("key", 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.StringBitCount("prefix:key", 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.StringBitCount("prefix:key", 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringBitOperation_1() public void StringBitOperation_1()
{ {
wrapper.StringBitOperation(Bitwise.Xor, "destination", "first", "second", CommandFlags.HighPriority); wrapper.StringBitOperation(Bitwise.Xor, "destination", "first", "second", CommandFlags.HighPriority);
mock.Verify(_ => _.StringBitOperation(Bitwise.Xor, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority)); mock.Verify(_ => _.StringBitOperation(Bitwise.Xor, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringBitOperation_2() public void StringBitOperation_2()
{ {
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.StringBitOperation(Bitwise.Xor, "destination", keys, CommandFlags.HighPriority); wrapper.StringBitOperation(Bitwise.Xor, "destination", keys, CommandFlags.HighPriority);
mock.Verify(_ => _.StringBitOperation(Bitwise.Xor, "prefix:destination", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.StringBitOperation(Bitwise.Xor, "prefix:destination", It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringBitPosition() public void StringBitPosition()
{ {
wrapper.StringBitPosition("key", true, 123, 456, CommandFlags.HighPriority); wrapper.StringBitPosition("key", true, 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.StringBitPosition("prefix:key", true, 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.StringBitPosition("prefix:key", true, 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringDecrement_1() public void StringDecrement_1()
{ {
wrapper.StringDecrement("key", 123, CommandFlags.HighPriority); wrapper.StringDecrement("key", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.StringDecrement("prefix:key", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.StringDecrement("prefix:key", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringDecrement_2() public void StringDecrement_2()
{ {
wrapper.StringDecrement("key", 1.23, CommandFlags.HighPriority); wrapper.StringDecrement("key", 1.23, CommandFlags.HighPriority);
mock.Verify(_ => _.StringDecrement("prefix:key", 1.23, CommandFlags.HighPriority)); mock.Verify(_ => _.StringDecrement("prefix:key", 1.23, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringGet_1() public void StringGet_1()
{ {
wrapper.StringGet("key", CommandFlags.HighPriority); wrapper.StringGet("key", CommandFlags.HighPriority);
mock.Verify(_ => _.StringGet("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.StringGet("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringGet_2() public void StringGet_2()
{ {
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.StringGet(keys, CommandFlags.HighPriority); wrapper.StringGet(keys, CommandFlags.HighPriority);
mock.Verify(_ => _.StringGet(It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.StringGet(It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringGetBit() public void StringGetBit()
{ {
wrapper.StringGetBit("key", 123, CommandFlags.HighPriority); wrapper.StringGetBit("key", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.StringGetBit("prefix:key", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.StringGetBit("prefix:key", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringGetRange() public void StringGetRange()
{ {
wrapper.StringGetRange("key", 123, 456, CommandFlags.HighPriority); wrapper.StringGetRange("key", 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.StringGetRange("prefix:key", 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.StringGetRange("prefix:key", 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringGetSet() public void StringGetSet()
{ {
wrapper.StringGetSet("key", "value", CommandFlags.HighPriority); wrapper.StringGetSet("key", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.StringGetSet("prefix:key", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.StringGetSet("prefix:key", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringGetWithExpiry() public void StringGetWithExpiry()
{ {
wrapper.StringGetWithExpiry("key", CommandFlags.HighPriority); wrapper.StringGetWithExpiry("key", CommandFlags.HighPriority);
mock.Verify(_ => _.StringGetWithExpiry("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.StringGetWithExpiry("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringIncrement_1() public void StringIncrement_1()
{ {
wrapper.StringIncrement("key", 123, CommandFlags.HighPriority); wrapper.StringIncrement("key", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.StringIncrement("prefix:key", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.StringIncrement("prefix:key", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringIncrement_2() public void StringIncrement_2()
{ {
wrapper.StringIncrement("key", 1.23, CommandFlags.HighPriority); wrapper.StringIncrement("key", 1.23, CommandFlags.HighPriority);
mock.Verify(_ => _.StringIncrement("prefix:key", 1.23, CommandFlags.HighPriority)); mock.Verify(_ => _.StringIncrement("prefix:key", 1.23, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringLength() public void StringLength()
{ {
wrapper.StringLength("key", CommandFlags.HighPriority); wrapper.StringLength("key", CommandFlags.HighPriority);
mock.Verify(_ => _.StringLength("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.StringLength("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringSet_1() public void StringSet_1()
{ {
TimeSpan expiry = TimeSpan.FromSeconds(123); TimeSpan expiry = TimeSpan.FromSeconds(123);
wrapper.StringSet("key", "value", expiry, When.Exists, CommandFlags.HighPriority); wrapper.StringSet("key", "value", expiry, When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.StringSet("prefix:key", "value", expiry, When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.StringSet("prefix:key", "value", expiry, When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringSet_2() public void StringSet_2()
{ {
KeyValuePair<RedisKey, RedisValue>[] values = new KeyValuePair<RedisKey, RedisValue>[] { new KeyValuePair<RedisKey, RedisValue>("a", "x"), new KeyValuePair<RedisKey, RedisValue>("b", "y") }; KeyValuePair<RedisKey, RedisValue>[] values = new KeyValuePair<RedisKey, RedisValue>[] { new KeyValuePair<RedisKey, RedisValue>("a", "x"), new KeyValuePair<RedisKey, RedisValue>("b", "y") };
Expression<Func<KeyValuePair<RedisKey, RedisValue>[], bool>> valid = _ => _.Length == 2 && _[0].Key == "prefix:a" && _[0].Value == "x" && _[1].Key == "prefix:b" && _[1].Value == "y"; Expression<Func<KeyValuePair<RedisKey, RedisValue>[], bool>> valid = _ => _.Length == 2 && _[0].Key == "prefix:a" && _[0].Value == "x" && _[1].Key == "prefix:b" && _[1].Value == "y";
wrapper.StringSet(values, When.Exists, CommandFlags.HighPriority); wrapper.StringSet(values, When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.StringSet(It.Is(valid), When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.StringSet(It.Is(valid), When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringSetBit() public void StringSetBit()
{ {
wrapper.StringSetBit("key", 123, true, CommandFlags.HighPriority); wrapper.StringSetBit("key", 123, true, CommandFlags.HighPriority);
mock.Verify(_ => _.StringSetBit("prefix:key", 123, true, CommandFlags.HighPriority)); mock.Verify(_ => _.StringSetBit("prefix:key", 123, true, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringSetRange() public void StringSetRange()
{ {
wrapper.StringSetRange("key", 123, "value", CommandFlags.HighPriority); wrapper.StringSetRange("key", 123, "value", CommandFlags.HighPriority);
mock.Verify(_ => _.StringSetRange("prefix:key", 123, "value", CommandFlags.HighPriority)); mock.Verify(_ => _.StringSetRange("prefix:key", 123, "value", CommandFlags.HighPriority));
} }
} }
} }
using System; using System;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace StackExchange.Redis.Tests.Issues namespace StackExchange.Redis.Tests.Issues
{ {
public class Issue25 : TestBase public class Issue25 : TestBase
{ {
public Issue25(ITestOutputHelper output) : base (output) { } public Issue25(ITestOutputHelper output) : base (output) { }
[Fact] [Fact]
public void CaseInsensitive() public void CaseInsensitive()
{ {
var options = ConfigurationOptions.Parse("ssl=true"); var options = ConfigurationOptions.Parse("ssl=true");
Assert.True(options.Ssl); Assert.True(options.Ssl);
Assert.Equal("ssl=True", options.ToString()); Assert.Equal("ssl=True", options.ToString());
options = ConfigurationOptions.Parse("SSL=TRUE"); options = ConfigurationOptions.Parse("SSL=TRUE");
Assert.True(options.Ssl); Assert.True(options.Ssl);
Assert.Equal("ssl=True", options.ToString()); Assert.Equal("ssl=True", options.ToString());
} }
[Fact] [Fact]
public void UnkonwnKeywordHandling_Ignore() public void UnkonwnKeywordHandling_Ignore()
{ {
var options = ConfigurationOptions.Parse("ssl2=true", true); var options = ConfigurationOptions.Parse("ssl2=true", true);
} }
[Fact] [Fact]
public void UnkonwnKeywordHandling_ExplicitFail() public void UnkonwnKeywordHandling_ExplicitFail()
{ {
var ex = Assert.Throws<ArgumentException>(() => { var ex = Assert.Throws<ArgumentException>(() => {
var options = ConfigurationOptions.Parse("ssl2=true", false); var options = ConfigurationOptions.Parse("ssl2=true", false);
}); });
Assert.Equal("Keyword 'ssl2' is not supported", ex.Message); Assert.Equal("Keyword 'ssl2' is not supported", ex.Message);
} }
[Fact] [Fact]
public void UnkonwnKeywordHandling_ImplicitFail() public void UnkonwnKeywordHandling_ImplicitFail()
{ {
var ex = Assert.Throws<ArgumentException>(() => { var ex = Assert.Throws<ArgumentException>(() => {
var options = ConfigurationOptions.Parse("ssl2=true"); var options = ConfigurationOptions.Parse("ssl2=true");
}); });
Assert.Equal("Keyword 'ssl2' is not supported", ex.Message); Assert.Equal("Keyword 'ssl2' is not supported", ex.Message);
} }
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
#if NETCOREAPP1_0 #if NETCOREAPP1_0
using System.Reflection; using System.Reflection;
#endif #endif
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading; using System.Threading;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
public class Profiling : TestBase public class Profiling : TestBase
{ {
public Profiling(ITestOutputHelper output) : base (output) { } public Profiling(ITestOutputHelper output) : base (output) { }
private class TestProfiler : IProfiler private class TestProfiler : IProfiler
{ {
public object MyContext = new object(); public object MyContext = new object();
public object GetContext() => MyContext; public object GetContext() => MyContext;
} }
[Fact] [Fact]
public void Simple() public void Simple()
{ {
using (var conn = Create()) using (var conn = Create())
{ {
var profiler = new TestProfiler(); var profiler = new TestProfiler();
conn.RegisterProfiler(profiler); conn.RegisterProfiler(profiler);
conn.BeginProfiling(profiler.MyContext); conn.BeginProfiling(profiler.MyContext);
var db = conn.GetDatabase(4); var db = conn.GetDatabase(4);
db.StringSet("hello", "world"); db.StringSet("hello", "world");
var val = db.StringGet("hello"); var val = db.StringGet("hello");
Assert.Equal("world", (string)val); Assert.Equal("world", (string)val);
var result=db.ScriptEvaluate(LuaScript.Prepare("return redis.call('get', @key)"), new { key = (RedisKey)"hello" }); var result=db.ScriptEvaluate(LuaScript.Prepare("return redis.call('get', @key)"), new { key = (RedisKey)"hello" });
Assert.Equal("world", result.AsString()); Assert.Equal("world", result.AsString());
var cmds = conn.FinishProfiling(profiler.MyContext); var cmds = conn.FinishProfiling(profiler.MyContext);
Assert.Equal(3, cmds.Count()); Assert.Equal(3, cmds.Count());
var set = cmds.SingleOrDefault(cmd => cmd.Command == "SET"); var set = cmds.SingleOrDefault(cmd => cmd.Command == "SET");
Assert.NotNull(set); Assert.NotNull(set);
var get = cmds.SingleOrDefault(cmd => cmd.Command == "GET"); var get = cmds.SingleOrDefault(cmd => cmd.Command == "GET");
Assert.NotNull(get); Assert.NotNull(get);
var eval = cmds.SingleOrDefault(cmd => cmd.Command == "EVAL"); var eval = cmds.SingleOrDefault(cmd => cmd.Command == "EVAL");
Assert.NotNull(eval); Assert.NotNull(eval);
Assert.True(set.CommandCreated <= get.CommandCreated); Assert.True(set.CommandCreated <= get.CommandCreated);
Assert.True(get.CommandCreated <= eval.CommandCreated); Assert.True(get.CommandCreated <= eval.CommandCreated);
AssertProfiledCommandValues(set, conn); AssertProfiledCommandValues(set, conn);
AssertProfiledCommandValues(get, conn); AssertProfiledCommandValues(get, conn);
AssertProfiledCommandValues(eval, conn); AssertProfiledCommandValues(eval, conn);
} }
} }
private static void AssertProfiledCommandValues(IProfiledCommand command, ConnectionMultiplexer conn) private static void AssertProfiledCommandValues(IProfiledCommand command, ConnectionMultiplexer conn)
{ {
Assert.Equal(4, command.Db); Assert.Equal(4, command.Db);
Assert.Equal(conn.GetEndPoints()[0], command.EndPoint); Assert.Equal(conn.GetEndPoints()[0], command.EndPoint);
Assert.True(command.CreationToEnqueued > TimeSpan.Zero); Assert.True(command.CreationToEnqueued > TimeSpan.Zero);
Assert.True(command.EnqueuedToSending > TimeSpan.Zero); Assert.True(command.EnqueuedToSending > TimeSpan.Zero);
Assert.True(command.SentToResponse > TimeSpan.Zero); Assert.True(command.SentToResponse > TimeSpan.Zero);
Assert.True(command.ResponseToCompletion > TimeSpan.Zero); Assert.True(command.ResponseToCompletion > TimeSpan.Zero);
Assert.True(command.ElapsedTime > TimeSpan.Zero); Assert.True(command.ElapsedTime > TimeSpan.Zero);
Assert.True(command.ElapsedTime > command.CreationToEnqueued && command.ElapsedTime > command.EnqueuedToSending && command.ElapsedTime > command.SentToResponse); Assert.True(command.ElapsedTime > command.CreationToEnqueued && command.ElapsedTime > command.EnqueuedToSending && command.ElapsedTime > command.SentToResponse);
Assert.True(command.RetransmissionOf == null); Assert.True(command.RetransmissionOf == null);
Assert.True(command.RetransmissionReason == null); Assert.True(command.RetransmissionReason == null);
} }
[Fact] [Fact]
public void ManyThreads() public void ManyThreads()
{ {
using (var conn = Create()) using (var conn = Create())
{ {
var profiler = new TestProfiler(); var profiler = new TestProfiler();
conn.RegisterProfiler(profiler); conn.RegisterProfiler(profiler);
conn.BeginProfiling(profiler.MyContext); conn.BeginProfiling(profiler.MyContext);
var threads = new List<Thread>(); var threads = new List<Thread>();
for (var i = 0; i < 16; i++) for (var i = 0; i < 16; i++)
{ {
var db = conn.GetDatabase(i); var db = conn.GetDatabase(i);
threads.Add(new Thread(() => threads.Add(new Thread(() =>
{ {
var threadTasks = new List<Task>(); var threadTasks = new List<Task>();
for (var j = 0; j < 1000; j++) for (var j = 0; j < 1000; j++)
{ {
var task = db.StringSetAsync("" + j, "" + j); var task = db.StringSetAsync("" + j, "" + j);
threadTasks.Add(task); threadTasks.Add(task);
} }
Task.WaitAll(threadTasks.ToArray()); Task.WaitAll(threadTasks.ToArray());
})); }));
} }
threads.ForEach(thread => thread.Start()); threads.ForEach(thread => thread.Start());
threads.ForEach(thread => thread.Join()); threads.ForEach(thread => thread.Join());
var allVals = conn.FinishProfiling(profiler.MyContext); var allVals = conn.FinishProfiling(profiler.MyContext);
var kinds = allVals.Select(cmd => cmd.Command).Distinct().ToList(); var kinds = allVals.Select(cmd => cmd.Command).Distinct().ToList();
Assert.True(kinds.Count <= 2); Assert.True(kinds.Count <= 2);
Assert.Contains("SET", kinds); Assert.Contains("SET", kinds);
if (kinds.Count == 2 && !kinds.Contains("SELECT")) if (kinds.Count == 2 && !kinds.Contains("SELECT"))
{ {
Assert.True(false, "Non-SET, Non-SELECT command seen"); Assert.True(false, "Non-SET, Non-SELECT command seen");
} }
Assert.Equal(16 * 1000, allVals.Count()); Assert.Equal(16 * 1000, allVals.Count());
Assert.Equal(16, allVals.Select(cmd => cmd.Db).Distinct().Count()); Assert.Equal(16, allVals.Select(cmd => cmd.Db).Distinct().Count());
for (var i = 0; i < 16; i++) for (var i = 0; i < 16; i++)
{ {
var setsInDb = allVals.Count(cmd => cmd.Db == i && cmd.Command == "SET"); var setsInDb = allVals.Count(cmd => cmd.Db == i && cmd.Command == "SET");
Assert.Equal(1000, setsInDb); Assert.Equal(1000, setsInDb);
} }
} }
} }
private class TestProfiler2 : IProfiler private class TestProfiler2 : IProfiler
{ {
private readonly ConcurrentDictionary<int, object> Contexts = new ConcurrentDictionary<int, object>(); private readonly ConcurrentDictionary<int, object> Contexts = new ConcurrentDictionary<int, object>();
public void RegisterContext(object context) public void RegisterContext(object context)
{ {
Contexts[Thread.CurrentThread.ManagedThreadId] = context; Contexts[Thread.CurrentThread.ManagedThreadId] = context;
} }
public object GetContext() public object GetContext()
{ {
if (!Contexts.TryGetValue(Thread.CurrentThread.ManagedThreadId, out object ret)) ret = null; if (!Contexts.TryGetValue(Thread.CurrentThread.ManagedThreadId, out object ret)) ret = null;
return ret; return ret;
} }
} }
[Fact] [Fact]
public void ManyContexts() public void ManyContexts()
{ {
using (var conn = Create()) using (var conn = Create())
{ {
var profiler = new TestProfiler2(); var profiler = new TestProfiler2();
conn.RegisterProfiler(profiler); conn.RegisterProfiler(profiler);
var perThreadContexts = new List<object>(); var perThreadContexts = new List<object>();
for (var i = 0; i < 16; i++) for (var i = 0; i < 16; i++)
{ {
perThreadContexts.Add(new object()); perThreadContexts.Add(new object());
} }
var threads = new List<Thread>(); var threads = new List<Thread>();
var results = new IEnumerable<IProfiledCommand>[16]; var results = new IEnumerable<IProfiledCommand>[16];
for (var i = 0; i < 16; i++) for (var i = 0; i < 16; i++)
{ {
var ix = i; var ix = i;
var thread = new Thread(() => var thread = new Thread(() =>
{ {
var ctx = perThreadContexts[ix]; var ctx = perThreadContexts[ix];
profiler.RegisterContext(ctx); profiler.RegisterContext(ctx);
conn.BeginProfiling(ctx); conn.BeginProfiling(ctx);
var db = conn.GetDatabase(ix); var db = conn.GetDatabase(ix);
var allTasks = new List<Task>(); var allTasks = new List<Task>();
for (var j = 0; j < 1000; j++) for (var j = 0; j < 1000; j++)
{ {
allTasks.Add(db.StringGetAsync("hello" + ix)); allTasks.Add(db.StringGetAsync("hello" + ix));
allTasks.Add(db.StringSetAsync("hello" + ix, "world" + ix)); allTasks.Add(db.StringSetAsync("hello" + ix, "world" + ix));
} }
Task.WaitAll(allTasks.ToArray()); Task.WaitAll(allTasks.ToArray());
results[ix] = conn.FinishProfiling(ctx); results[ix] = conn.FinishProfiling(ctx);
}); });
threads.Add(thread); threads.Add(thread);
} }
threads.ForEach(t => t.Start()); threads.ForEach(t => t.Start());
threads.ForEach(t => t.Join()); threads.ForEach(t => t.Join());
for (var i = 0; i < results.Length; i++) for (var i = 0; i < results.Length; i++)
{ {
var res = results[i]; var res = results[i];
Assert.NotNull(res); Assert.NotNull(res);
var numGets = res.Count(r => r.Command == "GET"); var numGets = res.Count(r => r.Command == "GET");
var numSets = res.Count(r => r.Command == "SET"); var numSets = res.Count(r => r.Command == "SET");
Assert.Equal(1000, numGets); Assert.Equal(1000, numGets);
Assert.Equal(1000, numSets); Assert.Equal(1000, numSets);
Assert.True(res.All(cmd => cmd.Db == i)); Assert.True(res.All(cmd => cmd.Db == i));
} }
} }
} }
private class TestProfiler3 : IProfiler private class TestProfiler3 : IProfiler
{ {
private readonly ConcurrentDictionary<int, object> Contexts = new ConcurrentDictionary<int, object>(); private readonly ConcurrentDictionary<int, object> Contexts = new ConcurrentDictionary<int, object>();
public void RegisterContext(object context) public void RegisterContext(object context)
{ {
Contexts[Thread.CurrentThread.ManagedThreadId] = context; Contexts[Thread.CurrentThread.ManagedThreadId] = context;
} }
public object AnyContext() => Contexts.First().Value; public object AnyContext() => Contexts.First().Value;
public void Reset() => Contexts.Clear(); public void Reset() => Contexts.Clear();
public object GetContext() public object GetContext()
{ {
if (!Contexts.TryGetValue(Thread.CurrentThread.ManagedThreadId, out object ret)) ret = null; if (!Contexts.TryGetValue(Thread.CurrentThread.ManagedThreadId, out object ret)) ret = null;
return ret; return ret;
} }
} }
// This is a separate method for target=DEBUG purposes. // This is a separate method for target=DEBUG purposes.
// In release builds, the runtime is smart enough to figure out // In release builds, the runtime is smart enough to figure out
// that the contexts are unreachable and should be collected but in // that the contexts are unreachable and should be collected but in
// debug builds... well, it's not very smart. // debug builds... well, it's not very smart.
private object LeaksCollectedAndRePooled_Initialize(ConnectionMultiplexer conn, int threadCount) private object LeaksCollectedAndRePooled_Initialize(ConnectionMultiplexer conn, int threadCount)
{ {
var profiler = new TestProfiler3(); var profiler = new TestProfiler3();
conn.RegisterProfiler(profiler); conn.RegisterProfiler(profiler);
var perThreadContexts = new List<object>(); var perThreadContexts = new List<object>();
for (var i = 0; i < threadCount; i++) for (var i = 0; i < threadCount; i++)
{ {
perThreadContexts.Add(new object()); perThreadContexts.Add(new object());
} }
var threads = new List<Thread>(); var threads = new List<Thread>();
var results = new IEnumerable<IProfiledCommand>[threadCount]; var results = new IEnumerable<IProfiledCommand>[threadCount];
for (var i = 0; i < threadCount; i++) for (var i = 0; i < threadCount; i++)
{ {
var ix = i; var ix = i;
var thread = new Thread(() => var thread = new Thread(() =>
{ {
var ctx = perThreadContexts[ix]; var ctx = perThreadContexts[ix];
profiler.RegisterContext(ctx); profiler.RegisterContext(ctx);
conn.BeginProfiling(ctx); conn.BeginProfiling(ctx);
var db = conn.GetDatabase(ix); var db = conn.GetDatabase(ix);
var allTasks = new List<Task>(); var allTasks = new List<Task>();
for (var j = 0; j < 1000; j++) for (var j = 0; j < 1000; j++)
{ {
allTasks.Add(db.StringGetAsync("hello" + ix)); allTasks.Add(db.StringGetAsync("hello" + ix));
allTasks.Add(db.StringSetAsync("hello" + ix, "world" + ix)); allTasks.Add(db.StringSetAsync("hello" + ix, "world" + ix));
} }
Task.WaitAll(allTasks.ToArray()); Task.WaitAll(allTasks.ToArray());
// intentionally leaking! // intentionally leaking!
}); });
threads.Add(thread); threads.Add(thread);
} }
threads.ForEach(t => t.Start()); threads.ForEach(t => t.Start());
threads.ForEach(t => t.Join()); threads.ForEach(t => t.Join());
var anyContext = profiler.AnyContext(); var anyContext = profiler.AnyContext();
profiler.Reset(); profiler.Reset();
return anyContext; return anyContext;
} }
[FactLongRunning] [FactLongRunning]
public void LeaksCollectedAndRePooled() public void LeaksCollectedAndRePooled()
{ {
const int ThreadCount = 16; const int ThreadCount = 16;
using (var conn = Create()) using (var conn = Create())
{ {
var anyContext = LeaksCollectedAndRePooled_Initialize(conn, ThreadCount); var anyContext = LeaksCollectedAndRePooled_Initialize(conn, ThreadCount);
// force collection of everything but `anyContext` // force collection of everything but `anyContext`
GC.Collect(3, GCCollectionMode.Forced, blocking: true); GC.Collect(3, GCCollectionMode.Forced, blocking: true);
GC.WaitForPendingFinalizers(); GC.WaitForPendingFinalizers();
Thread.Sleep(TimeSpan.FromMinutes(1.01)); Thread.Sleep(TimeSpan.FromMinutes(1.01));
conn.FinishProfiling(anyContext); conn.FinishProfiling(anyContext);
// make sure we haven't left anything in the active contexts dictionary // make sure we haven't left anything in the active contexts dictionary
Assert.Equal(0, conn.profiledCommands.ContextCount); Assert.Equal(0, conn.profiledCommands.ContextCount);
Assert.Equal(ThreadCount, ConcurrentProfileStorageCollection.AllocationCount); Assert.Equal(ThreadCount, ConcurrentProfileStorageCollection.AllocationCount);
Assert.Equal(ThreadCount, ConcurrentProfileStorageCollection.CountInPool()); Assert.Equal(ThreadCount, ConcurrentProfileStorageCollection.CountInPool());
} }
} }
[Fact] [Fact]
public void ReuseStorage() public void ReuseStorage()
{ {
const int ThreadCount = 16; const int ThreadCount = 16;
// have to reset so other tests don't clober // have to reset so other tests don't clober
ConcurrentProfileStorageCollection.AllocationCount = 0; ConcurrentProfileStorageCollection.AllocationCount = 0;
using (var conn = Create()) using (var conn = Create())
{ {
var profiler = new TestProfiler2(); var profiler = new TestProfiler2();
conn.RegisterProfiler(profiler); conn.RegisterProfiler(profiler);
var perThreadContexts = new List<object>(); var perThreadContexts = new List<object>();
for (var i = 0; i < 16; i++) for (var i = 0; i < 16; i++)
{ {
perThreadContexts.Add(new object()); perThreadContexts.Add(new object());
} }
var threads = new List<Thread>(); var threads = new List<Thread>();
var results = new List<IEnumerable<IProfiledCommand>>[16]; var results = new List<IEnumerable<IProfiledCommand>>[16];
for (var i = 0; i < 16; i++) for (var i = 0; i < 16; i++)
{ {
results[i] = new List<IEnumerable<IProfiledCommand>>(); results[i] = new List<IEnumerable<IProfiledCommand>>();
} }
for (var i = 0; i < ThreadCount; i++) for (var i = 0; i < ThreadCount; i++)
{ {
var ix = i; var ix = i;
var thread = new Thread(() => var thread = new Thread(() =>
{ {
for (var k = 0; k < 10; k++) for (var k = 0; k < 10; k++)
{ {
var ctx = perThreadContexts[ix]; var ctx = perThreadContexts[ix];
profiler.RegisterContext(ctx); profiler.RegisterContext(ctx);
conn.BeginProfiling(ctx); conn.BeginProfiling(ctx);
var db = conn.GetDatabase(ix); var db = conn.GetDatabase(ix);
var allTasks = new List<Task>(); var allTasks = new List<Task>();
for (var j = 0; j < 1000; j++) for (var j = 0; j < 1000; j++)
{ {
allTasks.Add(db.StringGetAsync("hello" + ix)); allTasks.Add(db.StringGetAsync("hello" + ix));
allTasks.Add(db.StringSetAsync("hello" + ix, "world" + ix)); allTasks.Add(db.StringSetAsync("hello" + ix, "world" + ix));
} }
Task.WaitAll(allTasks.ToArray()); Task.WaitAll(allTasks.ToArray());
results[ix].Add(conn.FinishProfiling(ctx)); results[ix].Add(conn.FinishProfiling(ctx));
} }
}); });
threads.Add(thread); threads.Add(thread);
} }
threads.ForEach(t => t.Start()); threads.ForEach(t => t.Start());
threads.ForEach(t => t.Join()); threads.ForEach(t => t.Join());
// only 16 allocations can ever be in flight at once // only 16 allocations can ever be in flight at once
var allocCount = ConcurrentProfileStorageCollection.AllocationCount; var allocCount = ConcurrentProfileStorageCollection.AllocationCount;
Assert.True(allocCount <= ThreadCount, allocCount.ToString()); Assert.True(allocCount <= ThreadCount, allocCount.ToString());
// correctness check for all allocations // correctness check for all allocations
for (var i = 0; i < results.Length; i++) for (var i = 0; i < results.Length; i++)
{ {
var resList = results[i]; var resList = results[i];
foreach (var res in resList) foreach (var res in resList)
{ {
Assert.NotNull(res); Assert.NotNull(res);
var numGets = res.Count(r => r.Command == "GET"); var numGets = res.Count(r => r.Command == "GET");
var numSets = res.Count(r => r.Command == "SET"); var numSets = res.Count(r => r.Command == "SET");
Assert.Equal(1000, numGets); Assert.Equal(1000, numGets);
Assert.Equal(1000, numSets); Assert.Equal(1000, numSets);
Assert.True(res.All(cmd => cmd.Db == i)); Assert.True(res.All(cmd => cmd.Db == i));
} }
} }
// no crossed streams // no crossed streams
var everything = results.SelectMany(r => r).ToList(); var everything = results.SelectMany(r => r).ToList();
for (var i = 0; i < everything.Count; i++) for (var i = 0; i < everything.Count; i++)
{ {
for (var j = 0; j < everything.Count; j++) for (var j = 0; j < everything.Count; j++)
{ {
if (i == j) continue; if (i == j) continue;
if (object.ReferenceEquals(everything[i], everything[j])) if (object.ReferenceEquals(everything[i], everything[j]))
{ {
Assert.True(false, "Profilings were jumbled"); Assert.True(false, "Profilings were jumbled");
} }
} }
} }
} }
} }
[Fact] [Fact]
public void LowAllocationEnumerable() public void LowAllocationEnumerable()
{ {
const int OuterLoop = 10000; const int OuterLoop = 10000;
using (var conn = Create()) using (var conn = Create())
{ {
var profiler = new TestProfiler(); var profiler = new TestProfiler();
conn.RegisterProfiler(profiler); conn.RegisterProfiler(profiler);
conn.BeginProfiling(profiler.MyContext); conn.BeginProfiling(profiler.MyContext);
var db = conn.GetDatabase(); var db = conn.GetDatabase();
var allTasks = new List<Task<string>>(); var allTasks = new List<Task<string>>();
foreach (var i in Enumerable.Range(0, OuterLoop)) foreach (var i in Enumerable.Range(0, OuterLoop))
{ {
var t = var t =
db.StringSetAsync("foo" + i, "bar" + i) db.StringSetAsync("foo" + i, "bar" + i)
.ContinueWith( .ContinueWith(
async _ => (string)(await db.StringGetAsync("foo" + i).ForAwait()) async _ => (string)(await db.StringGetAsync("foo" + i).ForAwait())
); );
var finalResult = t.Unwrap(); var finalResult = t.Unwrap();
allTasks.Add(finalResult); allTasks.Add(finalResult);
} }
conn.WaitAll(allTasks.ToArray()); conn.WaitAll(allTasks.ToArray());
var res = conn.FinishProfiling(profiler.MyContext); var res = conn.FinishProfiling(profiler.MyContext);
Assert.True(res.GetType().GetTypeInfo().IsValueType); Assert.True(res.GetType().GetTypeInfo().IsValueType);
using (var e = res.GetEnumerator()) using (var e = res.GetEnumerator())
{ {
Assert.True(e.GetType().GetTypeInfo().IsValueType); Assert.True(e.GetType().GetTypeInfo().IsValueType);
Assert.True(e.MoveNext()); Assert.True(e.MoveNext());
var i = e.Current; var i = e.Current;
e.Reset(); e.Reset();
Assert.True(e.MoveNext()); Assert.True(e.MoveNext());
var j = e.Current; var j = e.Current;
Assert.True(object.ReferenceEquals(i, j)); Assert.True(object.ReferenceEquals(i, j));
} }
Assert.Equal(OuterLoop * 2, res.Count()); Assert.Equal(OuterLoop * 2, res.Count());
Assert.Equal(OuterLoop, res.Count(r => r.Command == "GET")); Assert.Equal(OuterLoop, res.Count(r => r.Command == "GET"));
Assert.Equal(OuterLoop, res.Count(r => r.Command == "SET")); Assert.Equal(OuterLoop, res.Count(r => r.Command == "SET"));
} }
} }
private class ToyProfiler : IProfiler private class ToyProfiler : IProfiler
{ {
public ConcurrentDictionary<Thread, object> Contexts = new ConcurrentDictionary<Thread, object>(); public ConcurrentDictionary<Thread, object> Contexts = new ConcurrentDictionary<Thread, object>();
public object GetContext() public object GetContext()
{ {
if (!Contexts.TryGetValue(Thread.CurrentThread, out object ctx)) ctx = null; if (!Contexts.TryGetValue(Thread.CurrentThread, out object ctx)) ctx = null;
return ctx; return ctx;
} }
} }
[Fact] [Fact]
public void ProfilingMD_Ex1() public void ProfilingMD_Ex1()
{ {
using (var c = Create()) using (var c = Create())
{ {
ConnectionMultiplexer conn = c; ConnectionMultiplexer conn = c;
var profiler = new ToyProfiler(); var profiler = new ToyProfiler();
var thisGroupContext = new object(); var thisGroupContext = new object();
conn.RegisterProfiler(profiler); conn.RegisterProfiler(profiler);
var threads = new List<Thread>(); var threads = new List<Thread>();
for (var i = 0; i < 16; i++) for (var i = 0; i < 16; i++)
{ {
var db = conn.GetDatabase(i); var db = conn.GetDatabase(i);
var thread = new Thread(() => var thread = new Thread(() =>
{ {
var threadTasks = new List<Task>(); var threadTasks = new List<Task>();
for (var j = 0; j < 1000; j++) for (var j = 0; j < 1000; j++)
{ {
var task = db.StringSetAsync("" + j, "" + j); var task = db.StringSetAsync("" + j, "" + j);
threadTasks.Add(task); threadTasks.Add(task);
} }
Task.WaitAll(threadTasks.ToArray()); Task.WaitAll(threadTasks.ToArray());
}); });
profiler.Contexts[thread] = thisGroupContext; profiler.Contexts[thread] = thisGroupContext;
threads.Add(thread); threads.Add(thread);
} }
conn.BeginProfiling(thisGroupContext); conn.BeginProfiling(thisGroupContext);
threads.ForEach(thread => thread.Start()); threads.ForEach(thread => thread.Start());
threads.ForEach(thread => thread.Join()); threads.ForEach(thread => thread.Join());
IEnumerable<IProfiledCommand> timings = conn.FinishProfiling(thisGroupContext); IEnumerable<IProfiledCommand> timings = conn.FinishProfiling(thisGroupContext);
Assert.Equal(16000, timings.Count()); Assert.Equal(16000, timings.Count());
} }
} }
[Fact] [Fact]
public void ProfilingMD_Ex2() public void ProfilingMD_Ex2()
{ {
using (var c = Create()) using (var c = Create())
{ {
ConnectionMultiplexer conn = c; ConnectionMultiplexer conn = c;
var profiler = new ToyProfiler(); var profiler = new ToyProfiler();
conn.RegisterProfiler(profiler); conn.RegisterProfiler(profiler);
var threads = new List<Thread>(); var threads = new List<Thread>();
var perThreadTimings = new ConcurrentDictionary<Thread, List<IProfiledCommand>>(); var perThreadTimings = new ConcurrentDictionary<Thread, List<IProfiledCommand>>();
for (var i = 0; i < 16; i++) for (var i = 0; i < 16; i++)
{ {
var db = conn.GetDatabase(i); var db = conn.GetDatabase(i);
var thread = new Thread(() => var thread = new Thread(() =>
{ {
var threadTasks = new List<Task>(); var threadTasks = new List<Task>();
conn.BeginProfiling(Thread.CurrentThread); conn.BeginProfiling(Thread.CurrentThread);
for (var j = 0; j < 1000; j++) for (var j = 0; j < 1000; j++)
{ {
var task = db.StringSetAsync("" + j, "" + j); var task = db.StringSetAsync("" + j, "" + j);
threadTasks.Add(task); threadTasks.Add(task);
} }
Task.WaitAll(threadTasks.ToArray()); Task.WaitAll(threadTasks.ToArray());
perThreadTimings[Thread.CurrentThread] = conn.FinishProfiling(Thread.CurrentThread).ToList(); perThreadTimings[Thread.CurrentThread] = conn.FinishProfiling(Thread.CurrentThread).ToList();
}); });
profiler.Contexts[thread] = thread; profiler.Contexts[thread] = thread;
threads.Add(thread); threads.Add(thread);
} }
threads.ForEach(thread => thread.Start()); threads.ForEach(thread => thread.Start());
threads.ForEach(thread => thread.Join()); threads.ForEach(thread => thread.Join());
Assert.Equal(16, perThreadTimings.Count); Assert.Equal(16, perThreadTimings.Count);
Assert.True(perThreadTimings.All(kv => kv.Value.Count == 1000)); Assert.True(perThreadTimings.All(kv => kv.Value.Count == 1000));
} }
} }
} }
} }
\ No newline at end of file
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Net; using System.Net;
using System.Text; using System.Text;
using Moq; using Moq;
using StackExchange.Redis.KeyspaceIsolation; using StackExchange.Redis.KeyspaceIsolation;
using Xunit; using Xunit;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
public sealed class WrapperBaseTests public sealed class WrapperBaseTests
{ {
private readonly Mock<IDatabaseAsync> mock; private readonly Mock<IDatabaseAsync> mock;
private readonly WrapperBase<IDatabaseAsync> wrapper; private readonly WrapperBase<IDatabaseAsync> wrapper;
public WrapperBaseTests() public WrapperBaseTests()
{ {
mock = new Mock<IDatabaseAsync>(); mock = new Mock<IDatabaseAsync>();
wrapper = new WrapperBase<IDatabaseAsync>(mock.Object, Encoding.UTF8.GetBytes("prefix:")); wrapper = new WrapperBase<IDatabaseAsync>(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
} }
#pragma warning disable RCS1047 // Non-asynchronous method name should not end with 'Async'. #pragma warning disable RCS1047 // Non-asynchronous method name should not end with 'Async'.
[Fact] [Fact]
public void DebugObjectAsync() public void DebugObjectAsync()
{ {
wrapper.DebugObjectAsync("key", CommandFlags.HighPriority); wrapper.DebugObjectAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.DebugObjectAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.DebugObjectAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashDecrementAsync_1() public void HashDecrementAsync_1()
{ {
wrapper.HashDecrementAsync("key", "hashField", 123, CommandFlags.HighPriority); wrapper.HashDecrementAsync("key", "hashField", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.HashDecrementAsync("prefix:key", "hashField", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.HashDecrementAsync("prefix:key", "hashField", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashDecrementAsync_2() public void HashDecrementAsync_2()
{ {
wrapper.HashDecrementAsync("key", "hashField", 1.23, CommandFlags.HighPriority); wrapper.HashDecrementAsync("key", "hashField", 1.23, CommandFlags.HighPriority);
mock.Verify(_ => _.HashDecrementAsync("prefix:key", "hashField", 1.23, CommandFlags.HighPriority)); mock.Verify(_ => _.HashDecrementAsync("prefix:key", "hashField", 1.23, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashDeleteAsync_1() public void HashDeleteAsync_1()
{ {
wrapper.HashDeleteAsync("key", "hashField", CommandFlags.HighPriority); wrapper.HashDeleteAsync("key", "hashField", CommandFlags.HighPriority);
mock.Verify(_ => _.HashDeleteAsync("prefix:key", "hashField", CommandFlags.HighPriority)); mock.Verify(_ => _.HashDeleteAsync("prefix:key", "hashField", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashDeleteAsync_2() public void HashDeleteAsync_2()
{ {
RedisValue[] hashFields = new RedisValue[0]; RedisValue[] hashFields = new RedisValue[0];
wrapper.HashDeleteAsync("key", hashFields, CommandFlags.HighPriority); wrapper.HashDeleteAsync("key", hashFields, CommandFlags.HighPriority);
mock.Verify(_ => _.HashDeleteAsync("prefix:key", hashFields, CommandFlags.HighPriority)); mock.Verify(_ => _.HashDeleteAsync("prefix:key", hashFields, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashExistsAsync() public void HashExistsAsync()
{ {
wrapper.HashExistsAsync("key", "hashField", CommandFlags.HighPriority); wrapper.HashExistsAsync("key", "hashField", CommandFlags.HighPriority);
mock.Verify(_ => _.HashExistsAsync("prefix:key", "hashField", CommandFlags.HighPriority)); mock.Verify(_ => _.HashExistsAsync("prefix:key", "hashField", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashGetAllAsync() public void HashGetAllAsync()
{ {
wrapper.HashGetAllAsync("key", CommandFlags.HighPriority); wrapper.HashGetAllAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.HashGetAllAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.HashGetAllAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashGetAsync_1() public void HashGetAsync_1()
{ {
wrapper.HashGetAsync("key", "hashField", CommandFlags.HighPriority); wrapper.HashGetAsync("key", "hashField", CommandFlags.HighPriority);
mock.Verify(_ => _.HashGetAsync("prefix:key", "hashField", CommandFlags.HighPriority)); mock.Verify(_ => _.HashGetAsync("prefix:key", "hashField", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashGetAsync_2() public void HashGetAsync_2()
{ {
RedisValue[] hashFields = new RedisValue[0]; RedisValue[] hashFields = new RedisValue[0];
wrapper.HashGetAsync("key", hashFields, CommandFlags.HighPriority); wrapper.HashGetAsync("key", hashFields, CommandFlags.HighPriority);
mock.Verify(_ => _.HashGetAsync("prefix:key", hashFields, CommandFlags.HighPriority)); mock.Verify(_ => _.HashGetAsync("prefix:key", hashFields, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashIncrementAsync_1() public void HashIncrementAsync_1()
{ {
wrapper.HashIncrementAsync("key", "hashField", 123, CommandFlags.HighPriority); wrapper.HashIncrementAsync("key", "hashField", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.HashIncrementAsync("prefix:key", "hashField", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.HashIncrementAsync("prefix:key", "hashField", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashIncrementAsync_2() public void HashIncrementAsync_2()
{ {
wrapper.HashIncrementAsync("key", "hashField", 1.23, CommandFlags.HighPriority); wrapper.HashIncrementAsync("key", "hashField", 1.23, CommandFlags.HighPriority);
mock.Verify(_ => _.HashIncrementAsync("prefix:key", "hashField", 1.23, CommandFlags.HighPriority)); mock.Verify(_ => _.HashIncrementAsync("prefix:key", "hashField", 1.23, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashKeysAsync() public void HashKeysAsync()
{ {
wrapper.HashKeysAsync("key", CommandFlags.HighPriority); wrapper.HashKeysAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.HashKeysAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.HashKeysAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashLengthAsync() public void HashLengthAsync()
{ {
wrapper.HashLengthAsync("key", CommandFlags.HighPriority); wrapper.HashLengthAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.HashLengthAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.HashLengthAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashSetAsync_1() public void HashSetAsync_1()
{ {
HashEntry[] hashFields = new HashEntry[0]; HashEntry[] hashFields = new HashEntry[0];
wrapper.HashSetAsync("key", hashFields, CommandFlags.HighPriority); wrapper.HashSetAsync("key", hashFields, CommandFlags.HighPriority);
mock.Verify(_ => _.HashSetAsync("prefix:key", hashFields, CommandFlags.HighPriority)); mock.Verify(_ => _.HashSetAsync("prefix:key", hashFields, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashSetAsync_2() public void HashSetAsync_2()
{ {
wrapper.HashSetAsync("key", "hashField", "value", When.Exists, CommandFlags.HighPriority); wrapper.HashSetAsync("key", "hashField", "value", When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.HashSetAsync("prefix:key", "hashField", "value", When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.HashSetAsync("prefix:key", "hashField", "value", When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HashValuesAsync() public void HashValuesAsync()
{ {
wrapper.HashValuesAsync("key", CommandFlags.HighPriority); wrapper.HashValuesAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.HashValuesAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.HashValuesAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HyperLogLogAddAsync_1() public void HyperLogLogAddAsync_1()
{ {
wrapper.HyperLogLogAddAsync("key", "value", CommandFlags.HighPriority); wrapper.HyperLogLogAddAsync("key", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.HyperLogLogAddAsync("prefix:key", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.HyperLogLogAddAsync("prefix:key", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HyperLogLogAddAsync_2() public void HyperLogLogAddAsync_2()
{ {
var values = new RedisValue[0]; var values = new RedisValue[0];
wrapper.HyperLogLogAddAsync("key", values, CommandFlags.HighPriority); wrapper.HyperLogLogAddAsync("key", values, CommandFlags.HighPriority);
mock.Verify(_ => _.HyperLogLogAddAsync("prefix:key", values, CommandFlags.HighPriority)); mock.Verify(_ => _.HyperLogLogAddAsync("prefix:key", values, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HyperLogLogLengthAsync() public void HyperLogLogLengthAsync()
{ {
wrapper.HyperLogLogLengthAsync("key", CommandFlags.HighPriority); wrapper.HyperLogLogLengthAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.HyperLogLogLengthAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.HyperLogLogLengthAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HyperLogLogMergeAsync_1() public void HyperLogLogMergeAsync_1()
{ {
wrapper.HyperLogLogMergeAsync("destination", "first", "second", CommandFlags.HighPriority); wrapper.HyperLogLogMergeAsync("destination", "first", "second", CommandFlags.HighPriority);
mock.Verify(_ => _.HyperLogLogMergeAsync("prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority)); mock.Verify(_ => _.HyperLogLogMergeAsync("prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void HyperLogLogMergeAsync_2() public void HyperLogLogMergeAsync_2()
{ {
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.HyperLogLogMergeAsync("destination", keys, CommandFlags.HighPriority); wrapper.HyperLogLogMergeAsync("destination", keys, CommandFlags.HighPriority);
mock.Verify(_ => _.HyperLogLogMergeAsync("prefix:destination", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.HyperLogLogMergeAsync("prefix:destination", It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void IdentifyEndpointAsync() public void IdentifyEndpointAsync()
{ {
wrapper.IdentifyEndpointAsync("key", CommandFlags.HighPriority); wrapper.IdentifyEndpointAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.IdentifyEndpointAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.IdentifyEndpointAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void IsConnected() public void IsConnected()
{ {
wrapper.IsConnected("key", CommandFlags.HighPriority); wrapper.IsConnected("key", CommandFlags.HighPriority);
mock.Verify(_ => _.IsConnected("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.IsConnected("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyDeleteAsync_1() public void KeyDeleteAsync_1()
{ {
wrapper.KeyDeleteAsync("key", CommandFlags.HighPriority); wrapper.KeyDeleteAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.KeyDeleteAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.KeyDeleteAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyDeleteAsync_2() public void KeyDeleteAsync_2()
{ {
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.KeyDeleteAsync(keys, CommandFlags.HighPriority); wrapper.KeyDeleteAsync(keys, CommandFlags.HighPriority);
mock.Verify(_ => _.KeyDeleteAsync(It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.KeyDeleteAsync(It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyDumpAsync() public void KeyDumpAsync()
{ {
wrapper.KeyDumpAsync("key", CommandFlags.HighPriority); wrapper.KeyDumpAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.KeyDumpAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.KeyDumpAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyExistsAsync() public void KeyExistsAsync()
{ {
wrapper.KeyExistsAsync("key", CommandFlags.HighPriority); wrapper.KeyExistsAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.KeyExistsAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.KeyExistsAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyExpireAsync_1() public void KeyExpireAsync_1()
{ {
TimeSpan expiry = TimeSpan.FromSeconds(123); TimeSpan expiry = TimeSpan.FromSeconds(123);
wrapper.KeyExpireAsync("key", expiry, CommandFlags.HighPriority); wrapper.KeyExpireAsync("key", expiry, CommandFlags.HighPriority);
mock.Verify(_ => _.KeyExpireAsync("prefix:key", expiry, CommandFlags.HighPriority)); mock.Verify(_ => _.KeyExpireAsync("prefix:key", expiry, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyExpireAsync_2() public void KeyExpireAsync_2()
{ {
DateTime expiry = DateTime.Now; DateTime expiry = DateTime.Now;
wrapper.KeyExpireAsync("key", expiry, CommandFlags.HighPriority); wrapper.KeyExpireAsync("key", expiry, CommandFlags.HighPriority);
mock.Verify(_ => _.KeyExpireAsync("prefix:key", expiry, CommandFlags.HighPriority)); mock.Verify(_ => _.KeyExpireAsync("prefix:key", expiry, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyMigrateAsync() public void KeyMigrateAsync()
{ {
EndPoint toServer = new IPEndPoint(IPAddress.Loopback, 123); EndPoint toServer = new IPEndPoint(IPAddress.Loopback, 123);
wrapper.KeyMigrateAsync("key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.HighPriority); wrapper.KeyMigrateAsync("key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.HighPriority);
mock.Verify(_ => _.KeyMigrateAsync("prefix:key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.HighPriority)); mock.Verify(_ => _.KeyMigrateAsync("prefix:key", toServer, 123, 456, MigrateOptions.Copy, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyMoveAsync() public void KeyMoveAsync()
{ {
wrapper.KeyMoveAsync("key", 123, CommandFlags.HighPriority); wrapper.KeyMoveAsync("key", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.KeyMoveAsync("prefix:key", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.KeyMoveAsync("prefix:key", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyPersistAsync() public void KeyPersistAsync()
{ {
wrapper.KeyPersistAsync("key", CommandFlags.HighPriority); wrapper.KeyPersistAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.KeyPersistAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.KeyPersistAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public Task KeyRandomAsync() public Task KeyRandomAsync()
{ {
return Assert.ThrowsAsync<NotSupportedException>(() => wrapper.KeyRandomAsync()); return Assert.ThrowsAsync<NotSupportedException>(() => wrapper.KeyRandomAsync());
} }
[Fact] [Fact]
public void KeyRenameAsync() public void KeyRenameAsync()
{ {
wrapper.KeyRenameAsync("key", "newKey", When.Exists, CommandFlags.HighPriority); wrapper.KeyRenameAsync("key", "newKey", When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.KeyRenameAsync("prefix:key", "prefix:newKey", When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.KeyRenameAsync("prefix:key", "prefix:newKey", When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyRestoreAsync() public void KeyRestoreAsync()
{ {
Byte[] value = new Byte[0]; Byte[] value = new Byte[0];
TimeSpan expiry = TimeSpan.FromSeconds(123); TimeSpan expiry = TimeSpan.FromSeconds(123);
wrapper.KeyRestoreAsync("key", value, expiry, CommandFlags.HighPriority); wrapper.KeyRestoreAsync("key", value, expiry, CommandFlags.HighPriority);
mock.Verify(_ => _.KeyRestoreAsync("prefix:key", value, expiry, CommandFlags.HighPriority)); mock.Verify(_ => _.KeyRestoreAsync("prefix:key", value, expiry, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyTimeToLiveAsync() public void KeyTimeToLiveAsync()
{ {
wrapper.KeyTimeToLiveAsync("key", CommandFlags.HighPriority); wrapper.KeyTimeToLiveAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.KeyTimeToLiveAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.KeyTimeToLiveAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void KeyTypeAsync() public void KeyTypeAsync()
{ {
wrapper.KeyTypeAsync("key", CommandFlags.HighPriority); wrapper.KeyTypeAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.KeyTypeAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.KeyTypeAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListGetByIndexAsync() public void ListGetByIndexAsync()
{ {
wrapper.ListGetByIndexAsync("key", 123, CommandFlags.HighPriority); wrapper.ListGetByIndexAsync("key", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.ListGetByIndexAsync("prefix:key", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.ListGetByIndexAsync("prefix:key", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListInsertAfterAsync() public void ListInsertAfterAsync()
{ {
wrapper.ListInsertAfterAsync("key", "pivot", "value", CommandFlags.HighPriority); wrapper.ListInsertAfterAsync("key", "pivot", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.ListInsertAfterAsync("prefix:key", "pivot", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.ListInsertAfterAsync("prefix:key", "pivot", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListInsertBeforeAsync() public void ListInsertBeforeAsync()
{ {
wrapper.ListInsertBeforeAsync("key", "pivot", "value", CommandFlags.HighPriority); wrapper.ListInsertBeforeAsync("key", "pivot", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.ListInsertBeforeAsync("prefix:key", "pivot", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.ListInsertBeforeAsync("prefix:key", "pivot", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListLeftPopAsync() public void ListLeftPopAsync()
{ {
wrapper.ListLeftPopAsync("key", CommandFlags.HighPriority); wrapper.ListLeftPopAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.ListLeftPopAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.ListLeftPopAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListLeftPushAsync_1() public void ListLeftPushAsync_1()
{ {
wrapper.ListLeftPushAsync("key", "value", When.Exists, CommandFlags.HighPriority); wrapper.ListLeftPushAsync("key", "value", When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.ListLeftPushAsync("prefix:key", "value", When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.ListLeftPushAsync("prefix:key", "value", When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListLeftPushAsync_2() public void ListLeftPushAsync_2()
{ {
RedisValue[] values = new RedisValue[0]; RedisValue[] values = new RedisValue[0];
wrapper.ListLeftPushAsync("key", values, CommandFlags.HighPriority); wrapper.ListLeftPushAsync("key", values, CommandFlags.HighPriority);
mock.Verify(_ => _.ListLeftPushAsync("prefix:key", values, CommandFlags.HighPriority)); mock.Verify(_ => _.ListLeftPushAsync("prefix:key", values, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListLengthAsync() public void ListLengthAsync()
{ {
wrapper.ListLengthAsync("key", CommandFlags.HighPriority); wrapper.ListLengthAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.ListLengthAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.ListLengthAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListRangeAsync() public void ListRangeAsync()
{ {
wrapper.ListRangeAsync("key", 123, 456, CommandFlags.HighPriority); wrapper.ListRangeAsync("key", 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.ListRangeAsync("prefix:key", 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.ListRangeAsync("prefix:key", 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListRemoveAsync() public void ListRemoveAsync()
{ {
wrapper.ListRemoveAsync("key", "value", 123, CommandFlags.HighPriority); wrapper.ListRemoveAsync("key", "value", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.ListRemoveAsync("prefix:key", "value", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.ListRemoveAsync("prefix:key", "value", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListRightPopAsync() public void ListRightPopAsync()
{ {
wrapper.ListRightPopAsync("key", CommandFlags.HighPriority); wrapper.ListRightPopAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.ListRightPopAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.ListRightPopAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListRightPopLeftPushAsync() public void ListRightPopLeftPushAsync()
{ {
wrapper.ListRightPopLeftPushAsync("source", "destination", CommandFlags.HighPriority); wrapper.ListRightPopLeftPushAsync("source", "destination", CommandFlags.HighPriority);
mock.Verify(_ => _.ListRightPopLeftPushAsync("prefix:source", "prefix:destination", CommandFlags.HighPriority)); mock.Verify(_ => _.ListRightPopLeftPushAsync("prefix:source", "prefix:destination", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListRightPushAsync_1() public void ListRightPushAsync_1()
{ {
wrapper.ListRightPushAsync("key", "value", When.Exists, CommandFlags.HighPriority); wrapper.ListRightPushAsync("key", "value", When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.ListRightPushAsync("prefix:key", "value", When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.ListRightPushAsync("prefix:key", "value", When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListRightPushAsync_2() public void ListRightPushAsync_2()
{ {
RedisValue[] values = new RedisValue[0]; RedisValue[] values = new RedisValue[0];
wrapper.ListRightPushAsync("key", values, CommandFlags.HighPriority); wrapper.ListRightPushAsync("key", values, CommandFlags.HighPriority);
mock.Verify(_ => _.ListRightPushAsync("prefix:key", values, CommandFlags.HighPriority)); mock.Verify(_ => _.ListRightPushAsync("prefix:key", values, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListSetByIndexAsync() public void ListSetByIndexAsync()
{ {
wrapper.ListSetByIndexAsync("key", 123, "value", CommandFlags.HighPriority); wrapper.ListSetByIndexAsync("key", 123, "value", CommandFlags.HighPriority);
mock.Verify(_ => _.ListSetByIndexAsync("prefix:key", 123, "value", CommandFlags.HighPriority)); mock.Verify(_ => _.ListSetByIndexAsync("prefix:key", 123, "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ListTrimAsync() public void ListTrimAsync()
{ {
wrapper.ListTrimAsync("key", 123, 456, CommandFlags.HighPriority); wrapper.ListTrimAsync("key", 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.ListTrimAsync("prefix:key", 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.ListTrimAsync("prefix:key", 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void LockExtendAsync() public void LockExtendAsync()
{ {
TimeSpan expiry = TimeSpan.FromSeconds(123); TimeSpan expiry = TimeSpan.FromSeconds(123);
wrapper.LockExtendAsync("key", "value", expiry, CommandFlags.HighPriority); wrapper.LockExtendAsync("key", "value", expiry, CommandFlags.HighPriority);
mock.Verify(_ => _.LockExtendAsync("prefix:key", "value", expiry, CommandFlags.HighPriority)); mock.Verify(_ => _.LockExtendAsync("prefix:key", "value", expiry, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void LockQueryAsync() public void LockQueryAsync()
{ {
wrapper.LockQueryAsync("key", CommandFlags.HighPriority); wrapper.LockQueryAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.LockQueryAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.LockQueryAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void LockReleaseAsync() public void LockReleaseAsync()
{ {
wrapper.LockReleaseAsync("key", "value", CommandFlags.HighPriority); wrapper.LockReleaseAsync("key", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.LockReleaseAsync("prefix:key", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.LockReleaseAsync("prefix:key", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void LockTakeAsync() public void LockTakeAsync()
{ {
TimeSpan expiry = TimeSpan.FromSeconds(123); TimeSpan expiry = TimeSpan.FromSeconds(123);
wrapper.LockTakeAsync("key", "value", expiry, CommandFlags.HighPriority); wrapper.LockTakeAsync("key", "value", expiry, CommandFlags.HighPriority);
mock.Verify(_ => _.LockTakeAsync("prefix:key", "value", expiry, CommandFlags.HighPriority)); mock.Verify(_ => _.LockTakeAsync("prefix:key", "value", expiry, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void PublishAsync() public void PublishAsync()
{ {
wrapper.PublishAsync("channel", "message", CommandFlags.HighPriority); wrapper.PublishAsync("channel", "message", CommandFlags.HighPriority);
mock.Verify(_ => _.PublishAsync("prefix:channel", "message", CommandFlags.HighPriority)); mock.Verify(_ => _.PublishAsync("prefix:channel", "message", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ScriptEvaluateAsync_1() public void ScriptEvaluateAsync_1()
{ {
byte[] hash = new byte[0]; byte[] hash = new byte[0];
RedisValue[] values = new RedisValue[0]; RedisValue[] values = new RedisValue[0];
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.ScriptEvaluateAsync(hash, keys, values, CommandFlags.HighPriority); wrapper.ScriptEvaluateAsync(hash, keys, values, CommandFlags.HighPriority);
mock.Verify(_ => _.ScriptEvaluateAsync(hash, It.Is(valid), values, CommandFlags.HighPriority)); mock.Verify(_ => _.ScriptEvaluateAsync(hash, It.Is(valid), values, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void ScriptEvaluateAsync_2() public void ScriptEvaluateAsync_2()
{ {
RedisValue[] values = new RedisValue[0]; RedisValue[] values = new RedisValue[0];
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.ScriptEvaluateAsync("script", keys, values, CommandFlags.HighPriority); wrapper.ScriptEvaluateAsync("script", keys, values, CommandFlags.HighPriority);
mock.Verify(_ => _.ScriptEvaluateAsync("script", It.Is(valid), values, CommandFlags.HighPriority)); mock.Verify(_ => _.ScriptEvaluateAsync("script", It.Is(valid), values, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetAddAsync_1() public void SetAddAsync_1()
{ {
wrapper.SetAddAsync("key", "value", CommandFlags.HighPriority); wrapper.SetAddAsync("key", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.SetAddAsync("prefix:key", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.SetAddAsync("prefix:key", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetAddAsync_2() public void SetAddAsync_2()
{ {
RedisValue[] values = new RedisValue[0]; RedisValue[] values = new RedisValue[0];
wrapper.SetAddAsync("key", values, CommandFlags.HighPriority); wrapper.SetAddAsync("key", values, CommandFlags.HighPriority);
mock.Verify(_ => _.SetAddAsync("prefix:key", values, CommandFlags.HighPriority)); mock.Verify(_ => _.SetAddAsync("prefix:key", values, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetCombineAndStoreAsync_1() public void SetCombineAndStoreAsync_1()
{ {
wrapper.SetCombineAndStoreAsync(SetOperation.Intersect, "destination", "first", "second", CommandFlags.HighPriority); wrapper.SetCombineAndStoreAsync(SetOperation.Intersect, "destination", "first", "second", CommandFlags.HighPriority);
mock.Verify(_ => _.SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority)); mock.Verify(_ => _.SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetCombineAndStoreAsync_2() public void SetCombineAndStoreAsync_2()
{ {
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.SetCombineAndStoreAsync(SetOperation.Intersect, "destination", keys, CommandFlags.HighPriority); wrapper.SetCombineAndStoreAsync(SetOperation.Intersect, "destination", keys, CommandFlags.HighPriority);
mock.Verify(_ => _.SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetCombineAsync_1() public void SetCombineAsync_1()
{ {
wrapper.SetCombineAsync(SetOperation.Intersect, "first", "second", CommandFlags.HighPriority); wrapper.SetCombineAsync(SetOperation.Intersect, "first", "second", CommandFlags.HighPriority);
mock.Verify(_ => _.SetCombineAsync(SetOperation.Intersect, "prefix:first", "prefix:second", CommandFlags.HighPriority)); mock.Verify(_ => _.SetCombineAsync(SetOperation.Intersect, "prefix:first", "prefix:second", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetCombineAsync_2() public void SetCombineAsync_2()
{ {
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.SetCombineAsync(SetOperation.Intersect, keys, CommandFlags.HighPriority); wrapper.SetCombineAsync(SetOperation.Intersect, keys, CommandFlags.HighPriority);
mock.Verify(_ => _.SetCombineAsync(SetOperation.Intersect, It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.SetCombineAsync(SetOperation.Intersect, It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetContainsAsync() public void SetContainsAsync()
{ {
wrapper.SetContainsAsync("key", "value", CommandFlags.HighPriority); wrapper.SetContainsAsync("key", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.SetContainsAsync("prefix:key", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.SetContainsAsync("prefix:key", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetLengthAsync() public void SetLengthAsync()
{ {
wrapper.SetLengthAsync("key", CommandFlags.HighPriority); wrapper.SetLengthAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.SetLengthAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.SetLengthAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetMembersAsync() public void SetMembersAsync()
{ {
wrapper.SetMembersAsync("key", CommandFlags.HighPriority); wrapper.SetMembersAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.SetMembersAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.SetMembersAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetMoveAsync() public void SetMoveAsync()
{ {
wrapper.SetMoveAsync("source", "destination", "value", CommandFlags.HighPriority); wrapper.SetMoveAsync("source", "destination", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.SetMoveAsync("prefix:source", "prefix:destination", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.SetMoveAsync("prefix:source", "prefix:destination", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetPopAsync() public void SetPopAsync()
{ {
wrapper.SetPopAsync("key", CommandFlags.HighPriority); wrapper.SetPopAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.SetPopAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.SetPopAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetRandomMemberAsync() public void SetRandomMemberAsync()
{ {
wrapper.SetRandomMemberAsync("key", CommandFlags.HighPriority); wrapper.SetRandomMemberAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.SetRandomMemberAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.SetRandomMemberAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetRandomMembersAsync() public void SetRandomMembersAsync()
{ {
wrapper.SetRandomMembersAsync("key", 123, CommandFlags.HighPriority); wrapper.SetRandomMembersAsync("key", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.SetRandomMembersAsync("prefix:key", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.SetRandomMembersAsync("prefix:key", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetRemoveAsync_1() public void SetRemoveAsync_1()
{ {
wrapper.SetRemoveAsync("key", "value", CommandFlags.HighPriority); wrapper.SetRemoveAsync("key", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.SetRemoveAsync("prefix:key", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.SetRemoveAsync("prefix:key", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SetRemoveAsync_2() public void SetRemoveAsync_2()
{ {
RedisValue[] values = new RedisValue[0]; RedisValue[] values = new RedisValue[0];
wrapper.SetRemoveAsync("key", values, CommandFlags.HighPriority); wrapper.SetRemoveAsync("key", values, CommandFlags.HighPriority);
mock.Verify(_ => _.SetRemoveAsync("prefix:key", values, CommandFlags.HighPriority)); mock.Verify(_ => _.SetRemoveAsync("prefix:key", values, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortAndStoreAsync() public void SortAndStoreAsync()
{ {
RedisValue[] get = new RedisValue[] { "a", "#" }; RedisValue[] get = new RedisValue[] { "a", "#" };
Expression<Func<RedisValue[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#"; Expression<Func<RedisValue[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#";
wrapper.SortAndStoreAsync("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.HighPriority); wrapper.SortAndStoreAsync("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.HighPriority);
wrapper.SortAndStoreAsync("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.HighPriority); wrapper.SortAndStoreAsync("destination", "key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.HighPriority);
mock.Verify(_ => _.SortAndStoreAsync("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.SortAndStoreAsync("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", It.Is(valid), CommandFlags.HighPriority));
mock.Verify(_ => _.SortAndStoreAsync("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.SortAndStoreAsync("prefix:destination", "prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortAsync() public void SortAsync()
{ {
RedisValue[] get = new RedisValue[] { "a", "#" }; RedisValue[] get = new RedisValue[] { "a", "#" };
Expression<Func<RedisValue[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#"; Expression<Func<RedisValue[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "#";
wrapper.SortAsync("key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.HighPriority); wrapper.SortAsync("key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", get, CommandFlags.HighPriority);
wrapper.SortAsync("key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.HighPriority); wrapper.SortAsync("key", 123, 456, Order.Descending, SortType.Alphabetic, "by", get, CommandFlags.HighPriority);
mock.Verify(_ => _.SortAsync("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.SortAsync("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "nosort", It.Is(valid), CommandFlags.HighPriority));
mock.Verify(_ => _.SortAsync("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.SortAsync("prefix:key", 123, 456, Order.Descending, SortType.Alphabetic, "prefix:by", It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetAddAsync_1() public void SortedSetAddAsync_1()
{ {
wrapper.SortedSetAddAsync("key", "member", 1.23, When.Exists, CommandFlags.HighPriority); wrapper.SortedSetAddAsync("key", "member", 1.23, When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetAddAsync("prefix:key", "member", 1.23, When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetAddAsync("prefix:key", "member", 1.23, When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetAddAsync_2() public void SortedSetAddAsync_2()
{ {
SortedSetEntry[] values = new SortedSetEntry[0]; SortedSetEntry[] values = new SortedSetEntry[0];
wrapper.SortedSetAddAsync("key", values, When.Exists, CommandFlags.HighPriority); wrapper.SortedSetAddAsync("key", values, When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetAddAsync("prefix:key", values, When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetAddAsync("prefix:key", values, When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetCombineAndStoreAsync_1() public void SortedSetCombineAndStoreAsync_1()
{ {
wrapper.SortedSetCombineAndStoreAsync(SetOperation.Intersect, "destination", "first", "second", Aggregate.Max, CommandFlags.HighPriority); wrapper.SortedSetCombineAndStoreAsync(SetOperation.Intersect, "destination", "first", "second", Aggregate.Max, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", Aggregate.Max, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", "prefix:first", "prefix:second", Aggregate.Max, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetCombineAndStoreAsync_2() public void SortedSetCombineAndStoreAsync_2()
{ {
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.SetCombineAndStoreAsync(SetOperation.Intersect, "destination", keys, CommandFlags.HighPriority); wrapper.SetCombineAndStoreAsync(SetOperation.Intersect, "destination", keys, CommandFlags.HighPriority);
mock.Verify(_ => _.SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.SetCombineAndStoreAsync(SetOperation.Intersect, "prefix:destination", It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetDecrementAsync() public void SortedSetDecrementAsync()
{ {
wrapper.SortedSetDecrementAsync("key", "member", 1.23, CommandFlags.HighPriority); wrapper.SortedSetDecrementAsync("key", "member", 1.23, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetDecrementAsync("prefix:key", "member", 1.23, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetDecrementAsync("prefix:key", "member", 1.23, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetIncrementAsync() public void SortedSetIncrementAsync()
{ {
wrapper.SortedSetIncrementAsync("key", "member", 1.23, CommandFlags.HighPriority); wrapper.SortedSetIncrementAsync("key", "member", 1.23, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetIncrementAsync("prefix:key", "member", 1.23, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetIncrementAsync("prefix:key", "member", 1.23, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetLengthAsync() public void SortedSetLengthAsync()
{ {
wrapper.SortedSetLengthAsync("key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority); wrapper.SortedSetLengthAsync("key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetLengthAsync("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetLengthAsync("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetLengthByValueAsync() public void SortedSetLengthByValueAsync()
{ {
wrapper.SortedSetLengthByValueAsync("key", "min", "max", Exclude.Start, CommandFlags.HighPriority); wrapper.SortedSetLengthByValueAsync("key", "min", "max", Exclude.Start, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetLengthByValueAsync("prefix:key", "min", "max", Exclude.Start, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetLengthByValueAsync("prefix:key", "min", "max", Exclude.Start, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRangeByRankAsync() public void SortedSetRangeByRankAsync()
{ {
wrapper.SortedSetRangeByRankAsync("key", 123, 456, Order.Descending, CommandFlags.HighPriority); wrapper.SortedSetRangeByRankAsync("key", 123, 456, Order.Descending, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRangeByRankAsync("prefix:key", 123, 456, Order.Descending, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRangeByRankAsync("prefix:key", 123, 456, Order.Descending, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRangeByRankWithScoresAsync() public void SortedSetRangeByRankWithScoresAsync()
{ {
wrapper.SortedSetRangeByRankWithScoresAsync("key", 123, 456, Order.Descending, CommandFlags.HighPriority); wrapper.SortedSetRangeByRankWithScoresAsync("key", 123, 456, Order.Descending, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRangeByRankWithScoresAsync("prefix:key", 123, 456, Order.Descending, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRangeByRankWithScoresAsync("prefix:key", 123, 456, Order.Descending, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRangeByScoreAsync() public void SortedSetRangeByScoreAsync()
{ {
wrapper.SortedSetRangeByScoreAsync("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority); wrapper.SortedSetRangeByScoreAsync("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRangeByScoreAsync("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRangeByScoreAsync("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRangeByScoreWithScoresAsync() public void SortedSetRangeByScoreWithScoresAsync()
{ {
wrapper.SortedSetRangeByScoreWithScoresAsync("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority); wrapper.SortedSetRangeByScoreWithScoresAsync("key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRangeByScoreWithScoresAsync("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRangeByScoreWithScoresAsync("prefix:key", 1.23, 1.23, Exclude.Start, Order.Descending, 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRangeByValueAsync() public void SortedSetRangeByValueAsync()
{ {
wrapper.SortedSetRangeByValueAsync("key", "min", "max", Exclude.Start, 123, 456, CommandFlags.HighPriority); wrapper.SortedSetRangeByValueAsync("key", "min", "max", Exclude.Start, 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRangeByValueAsync("prefix:key", "min", "max", Exclude.Start, 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRangeByValueAsync("prefix:key", "min", "max", Exclude.Start, 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRankAsync() public void SortedSetRankAsync()
{ {
wrapper.SortedSetRankAsync("key", "member", Order.Descending, CommandFlags.HighPriority); wrapper.SortedSetRankAsync("key", "member", Order.Descending, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRankAsync("prefix:key", "member", Order.Descending, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRankAsync("prefix:key", "member", Order.Descending, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRemoveAsync_1() public void SortedSetRemoveAsync_1()
{ {
wrapper.SortedSetRemoveAsync("key", "member", CommandFlags.HighPriority); wrapper.SortedSetRemoveAsync("key", "member", CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRemoveAsync("prefix:key", "member", CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRemoveAsync("prefix:key", "member", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRemoveAsync_2() public void SortedSetRemoveAsync_2()
{ {
RedisValue[] members = new RedisValue[0]; RedisValue[] members = new RedisValue[0];
wrapper.SortedSetRemoveAsync("key", members, CommandFlags.HighPriority); wrapper.SortedSetRemoveAsync("key", members, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRemoveAsync("prefix:key", members, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRemoveAsync("prefix:key", members, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRemoveRangeByRankAsync() public void SortedSetRemoveRangeByRankAsync()
{ {
wrapper.SortedSetRemoveRangeByRankAsync("key", 123, 456, CommandFlags.HighPriority); wrapper.SortedSetRemoveRangeByRankAsync("key", 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRemoveRangeByRankAsync("prefix:key", 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRemoveRangeByRankAsync("prefix:key", 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRemoveRangeByScoreAsync() public void SortedSetRemoveRangeByScoreAsync()
{ {
wrapper.SortedSetRemoveRangeByScoreAsync("key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority); wrapper.SortedSetRemoveRangeByScoreAsync("key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRemoveRangeByScoreAsync("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRemoveRangeByScoreAsync("prefix:key", 1.23, 1.23, Exclude.Start, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetRemoveRangeByValueAsync() public void SortedSetRemoveRangeByValueAsync()
{ {
wrapper.SortedSetRemoveRangeByValueAsync("key", "min", "max", Exclude.Start, CommandFlags.HighPriority); wrapper.SortedSetRemoveRangeByValueAsync("key", "min", "max", Exclude.Start, CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetRemoveRangeByValueAsync("prefix:key", "min", "max", Exclude.Start, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetRemoveRangeByValueAsync("prefix:key", "min", "max", Exclude.Start, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void SortedSetScoreAsync() public void SortedSetScoreAsync()
{ {
wrapper.SortedSetScoreAsync("key", "member", CommandFlags.HighPriority); wrapper.SortedSetScoreAsync("key", "member", CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetScoreAsync("prefix:key", "member", CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetScoreAsync("prefix:key", "member", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringAppendAsync() public void StringAppendAsync()
{ {
wrapper.StringAppendAsync("key", "value", CommandFlags.HighPriority); wrapper.StringAppendAsync("key", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.StringAppendAsync("prefix:key", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.StringAppendAsync("prefix:key", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringBitCountAsync() public void StringBitCountAsync()
{ {
wrapper.StringBitCountAsync("key", 123, 456, CommandFlags.HighPriority); wrapper.StringBitCountAsync("key", 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.StringBitCountAsync("prefix:key", 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.StringBitCountAsync("prefix:key", 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringBitOperationAsync_1() public void StringBitOperationAsync_1()
{ {
wrapper.StringBitOperationAsync(Bitwise.Xor, "destination", "first", "second", CommandFlags.HighPriority); wrapper.StringBitOperationAsync(Bitwise.Xor, "destination", "first", "second", CommandFlags.HighPriority);
mock.Verify(_ => _.StringBitOperationAsync(Bitwise.Xor, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority)); mock.Verify(_ => _.StringBitOperationAsync(Bitwise.Xor, "prefix:destination", "prefix:first", "prefix:second", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringBitOperationAsync_2() public void StringBitOperationAsync_2()
{ {
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.StringBitOperationAsync(Bitwise.Xor, "destination", keys, CommandFlags.HighPriority); wrapper.StringBitOperationAsync(Bitwise.Xor, "destination", keys, CommandFlags.HighPriority);
mock.Verify(_ => _.StringBitOperationAsync(Bitwise.Xor, "prefix:destination", It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.StringBitOperationAsync(Bitwise.Xor, "prefix:destination", It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringBitPositionAsync() public void StringBitPositionAsync()
{ {
wrapper.StringBitPositionAsync("key", true, 123, 456, CommandFlags.HighPriority); wrapper.StringBitPositionAsync("key", true, 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.StringBitPositionAsync("prefix:key", true, 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.StringBitPositionAsync("prefix:key", true, 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringDecrementAsync_1() public void StringDecrementAsync_1()
{ {
wrapper.StringDecrementAsync("key", 123, CommandFlags.HighPriority); wrapper.StringDecrementAsync("key", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.StringDecrementAsync("prefix:key", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.StringDecrementAsync("prefix:key", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringDecrementAsync_2() public void StringDecrementAsync_2()
{ {
wrapper.StringDecrementAsync("key", 1.23, CommandFlags.HighPriority); wrapper.StringDecrementAsync("key", 1.23, CommandFlags.HighPriority);
mock.Verify(_ => _.StringDecrementAsync("prefix:key", 1.23, CommandFlags.HighPriority)); mock.Verify(_ => _.StringDecrementAsync("prefix:key", 1.23, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringGetAsync_1() public void StringGetAsync_1()
{ {
wrapper.StringGetAsync("key", CommandFlags.HighPriority); wrapper.StringGetAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.StringGetAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.StringGetAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringGetAsync_2() public void StringGetAsync_2()
{ {
RedisKey[] keys = new RedisKey[] { "a", "b" }; RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b"; Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.StringGetAsync(keys, CommandFlags.HighPriority); wrapper.StringGetAsync(keys, CommandFlags.HighPriority);
mock.Verify(_ => _.StringGetAsync(It.Is(valid), CommandFlags.HighPriority)); mock.Verify(_ => _.StringGetAsync(It.Is(valid), CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringGetBitAsync() public void StringGetBitAsync()
{ {
wrapper.StringGetBitAsync("key", 123, CommandFlags.HighPriority); wrapper.StringGetBitAsync("key", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.StringGetBitAsync("prefix:key", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.StringGetBitAsync("prefix:key", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringGetRangeAsync() public void StringGetRangeAsync()
{ {
wrapper.StringGetRangeAsync("key", 123, 456, CommandFlags.HighPriority); wrapper.StringGetRangeAsync("key", 123, 456, CommandFlags.HighPriority);
mock.Verify(_ => _.StringGetRangeAsync("prefix:key", 123, 456, CommandFlags.HighPriority)); mock.Verify(_ => _.StringGetRangeAsync("prefix:key", 123, 456, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringGetSetAsync() public void StringGetSetAsync()
{ {
wrapper.StringGetSetAsync("key", "value", CommandFlags.HighPriority); wrapper.StringGetSetAsync("key", "value", CommandFlags.HighPriority);
mock.Verify(_ => _.StringGetSetAsync("prefix:key", "value", CommandFlags.HighPriority)); mock.Verify(_ => _.StringGetSetAsync("prefix:key", "value", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringGetWithExpiryAsync() public void StringGetWithExpiryAsync()
{ {
wrapper.StringGetWithExpiryAsync("key", CommandFlags.HighPriority); wrapper.StringGetWithExpiryAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.StringGetWithExpiryAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.StringGetWithExpiryAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringIncrementAsync_1() public void StringIncrementAsync_1()
{ {
wrapper.StringIncrementAsync("key", 123, CommandFlags.HighPriority); wrapper.StringIncrementAsync("key", 123, CommandFlags.HighPriority);
mock.Verify(_ => _.StringIncrementAsync("prefix:key", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.StringIncrementAsync("prefix:key", 123, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringIncrementAsync_2() public void StringIncrementAsync_2()
{ {
wrapper.StringIncrementAsync("key", 1.23, CommandFlags.HighPriority); wrapper.StringIncrementAsync("key", 1.23, CommandFlags.HighPriority);
mock.Verify(_ => _.StringIncrementAsync("prefix:key", 1.23, CommandFlags.HighPriority)); mock.Verify(_ => _.StringIncrementAsync("prefix:key", 1.23, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringLengthAsync() public void StringLengthAsync()
{ {
wrapper.StringLengthAsync("key", CommandFlags.HighPriority); wrapper.StringLengthAsync("key", CommandFlags.HighPriority);
mock.Verify(_ => _.StringLengthAsync("prefix:key", CommandFlags.HighPriority)); mock.Verify(_ => _.StringLengthAsync("prefix:key", CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringSetAsync_1() public void StringSetAsync_1()
{ {
TimeSpan expiry = TimeSpan.FromSeconds(123); TimeSpan expiry = TimeSpan.FromSeconds(123);
wrapper.StringSetAsync("key", "value", expiry, When.Exists, CommandFlags.HighPriority); wrapper.StringSetAsync("key", "value", expiry, When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.StringSetAsync("prefix:key", "value", expiry, When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.StringSetAsync("prefix:key", "value", expiry, When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringSetAsync_2() public void StringSetAsync_2()
{ {
KeyValuePair<RedisKey, RedisValue>[] values = new KeyValuePair<RedisKey, RedisValue>[] { new KeyValuePair<RedisKey, RedisValue>("a", "x"), new KeyValuePair<RedisKey, RedisValue>("b", "y") }; KeyValuePair<RedisKey, RedisValue>[] values = new KeyValuePair<RedisKey, RedisValue>[] { new KeyValuePair<RedisKey, RedisValue>("a", "x"), new KeyValuePair<RedisKey, RedisValue>("b", "y") };
Expression<Func<KeyValuePair<RedisKey, RedisValue>[], bool>> valid = _ => _.Length == 2 && _[0].Key == "prefix:a" && _[0].Value == "x" && _[1].Key == "prefix:b" && _[1].Value == "y"; Expression<Func<KeyValuePair<RedisKey, RedisValue>[], bool>> valid = _ => _.Length == 2 && _[0].Key == "prefix:a" && _[0].Value == "x" && _[1].Key == "prefix:b" && _[1].Value == "y";
wrapper.StringSetAsync(values, When.Exists, CommandFlags.HighPriority); wrapper.StringSetAsync(values, When.Exists, CommandFlags.HighPriority);
mock.Verify(_ => _.StringSetAsync(It.Is(valid), When.Exists, CommandFlags.HighPriority)); mock.Verify(_ => _.StringSetAsync(It.Is(valid), When.Exists, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringSetBitAsync() public void StringSetBitAsync()
{ {
wrapper.StringSetBitAsync("key", 123, true, CommandFlags.HighPriority); wrapper.StringSetBitAsync("key", 123, true, CommandFlags.HighPriority);
mock.Verify(_ => _.StringSetBitAsync("prefix:key", 123, true, CommandFlags.HighPriority)); mock.Verify(_ => _.StringSetBitAsync("prefix:key", 123, true, CommandFlags.HighPriority));
} }
[Fact] [Fact]
public void StringSetRangeAsync() public void StringSetRangeAsync()
{ {
wrapper.StringSetRangeAsync("key", 123, "value", CommandFlags.HighPriority); wrapper.StringSetRangeAsync("key", 123, "value", CommandFlags.HighPriority);
mock.Verify(_ => _.StringSetRangeAsync("prefix:key", 123, "value", CommandFlags.HighPriority)); mock.Verify(_ => _.StringSetRangeAsync("prefix:key", 123, "value", CommandFlags.HighPriority));
} }
#pragma warning restore RCS1047 // Non-asynchronous method name should not end with 'Async'. #pragma warning restore RCS1047 // Non-asynchronous method name should not end with 'Async'.
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
internal sealed partial class CompletionManager internal sealed partial class CompletionManager
{ {
private static readonly WaitCallback processAsyncCompletionQueue = ProcessAsyncCompletionQueue, private static readonly WaitCallback processAsyncCompletionQueue = ProcessAsyncCompletionQueue,
anyOrderCompletionHandler = AnyOrderCompletionHandler; anyOrderCompletionHandler = AnyOrderCompletionHandler;
private readonly Queue<ICompletable> asyncCompletionQueue = new Queue<ICompletable>(); private readonly Queue<ICompletable> asyncCompletionQueue = new Queue<ICompletable>();
private readonly ConnectionMultiplexer multiplexer; private readonly ConnectionMultiplexer multiplexer;
private readonly string name; private readonly string name;
private int activeAsyncWorkerThread = 0; private int activeAsyncWorkerThread = 0;
private long completedSync, completedAsync, failedAsync; private long completedSync, completedAsync, failedAsync;
public CompletionManager(ConnectionMultiplexer multiplexer, string name) public CompletionManager(ConnectionMultiplexer multiplexer, string name)
{ {
this.multiplexer = multiplexer; this.multiplexer = multiplexer;
this.name = name; this.name = name;
} }
public void CompleteSyncOrAsync(ICompletable operation) public void CompleteSyncOrAsync(ICompletable operation)
{ {
if (operation == null) return; if (operation == null) return;
if (operation.TryComplete(false)) if (operation.TryComplete(false))
{ {
multiplexer.Trace("Completed synchronously: " + operation, name); multiplexer.Trace("Completed synchronously: " + operation, name);
Interlocked.Increment(ref completedSync); Interlocked.Increment(ref completedSync);
} }
else else
{ {
if (multiplexer.PreserveAsyncOrder) if (multiplexer.PreserveAsyncOrder)
{ {
multiplexer.Trace("Queueing for asynchronous completion", name); multiplexer.Trace("Queueing for asynchronous completion", name);
bool startNewWorker; bool startNewWorker;
lock (asyncCompletionQueue) lock (asyncCompletionQueue)
{ {
asyncCompletionQueue.Enqueue(operation); asyncCompletionQueue.Enqueue(operation);
startNewWorker = asyncCompletionQueue.Count == 1; startNewWorker = asyncCompletionQueue.Count == 1;
} }
if (startNewWorker) if (startNewWorker)
{ {
multiplexer.Trace("Starting new async completion worker", name); multiplexer.Trace("Starting new async completion worker", name);
OnCompletedAsync(); OnCompletedAsync();
ThreadPool.QueueUserWorkItem(processAsyncCompletionQueue, this); ThreadPool.QueueUserWorkItem(processAsyncCompletionQueue, this);
} }
} else } else
{ {
multiplexer.Trace("Using thread-pool for asynchronous completion", name); multiplexer.Trace("Using thread-pool for asynchronous completion", name);
ThreadPool.QueueUserWorkItem(anyOrderCompletionHandler, operation); ThreadPool.QueueUserWorkItem(anyOrderCompletionHandler, operation);
Interlocked.Increment(ref completedAsync); // k, *technically* we haven't actually completed this yet, but: close enough Interlocked.Increment(ref completedAsync); // k, *technically* we haven't actually completed this yet, but: close enough
} }
} }
} }
internal void GetCounters(ConnectionCounters counters) internal void GetCounters(ConnectionCounters counters)
{ {
lock (asyncCompletionQueue) lock (asyncCompletionQueue)
{ {
counters.ResponsesAwaitingAsyncCompletion = asyncCompletionQueue.Count; counters.ResponsesAwaitingAsyncCompletion = asyncCompletionQueue.Count;
} }
counters.CompletedSynchronously = Interlocked.Read(ref completedSync); counters.CompletedSynchronously = Interlocked.Read(ref completedSync);
counters.CompletedAsynchronously = Interlocked.Read(ref completedAsync); counters.CompletedAsynchronously = Interlocked.Read(ref completedAsync);
counters.FailedAsynchronously = Interlocked.Read(ref failedAsync); counters.FailedAsynchronously = Interlocked.Read(ref failedAsync);
} }
internal int GetOutstandingCount() internal int GetOutstandingCount()
{ {
lock(asyncCompletionQueue) lock(asyncCompletionQueue)
{ {
return asyncCompletionQueue.Count; return asyncCompletionQueue.Count;
} }
} }
internal void GetStormLog(StringBuilder sb) internal void GetStormLog(StringBuilder sb)
{ {
lock(asyncCompletionQueue) lock(asyncCompletionQueue)
{ {
if (asyncCompletionQueue.Count == 0) return; if (asyncCompletionQueue.Count == 0) return;
sb.Append("Response awaiting completion: ").Append(asyncCompletionQueue.Count).AppendLine(); sb.Append("Response awaiting completion: ").Append(asyncCompletionQueue.Count).AppendLine();
int total = 0; int total = 0;
foreach(var item in asyncCompletionQueue) foreach(var item in asyncCompletionQueue)
{ {
if (++total >= 500) break; if (++total >= 500) break;
item.AppendStormLog(sb); item.AppendStormLog(sb);
sb.AppendLine(); sb.AppendLine();
} }
} }
} }
private static void AnyOrderCompletionHandler(object state) private static void AnyOrderCompletionHandler(object state)
{ {
try try
{ {
ConnectionMultiplexer.TraceWithoutContext("Completing async (any order): " + state); ConnectionMultiplexer.TraceWithoutContext("Completing async (any order): " + state);
((ICompletable)state).TryComplete(true); ((ICompletable)state).TryComplete(true);
} }
catch (Exception ex) catch (Exception ex)
{ {
ConnectionMultiplexer.TraceWithoutContext("Async completion error: " + ex.Message); ConnectionMultiplexer.TraceWithoutContext("Async completion error: " + ex.Message);
} }
} }
private static void ProcessAsyncCompletionQueue(object state) private static void ProcessAsyncCompletionQueue(object state)
{ {
((CompletionManager)state).ProcessAsyncCompletionQueueImpl(); ((CompletionManager)state).ProcessAsyncCompletionQueueImpl();
} }
partial void OnCompletedAsync(); partial void OnCompletedAsync();
private void ProcessAsyncCompletionQueueImpl() private void ProcessAsyncCompletionQueueImpl()
{ {
int currentThread = Environment.CurrentManagedThreadId; int currentThread = Environment.CurrentManagedThreadId;
try try
{ {
while (Interlocked.CompareExchange(ref activeAsyncWorkerThread, currentThread, 0) != 0) while (Interlocked.CompareExchange(ref activeAsyncWorkerThread, currentThread, 0) != 0)
{ {
// if we don't win the lock, check whether there is still work; if there is we // if we don't win the lock, check whether there is still work; if there is we
// need to retry to prevent a nasty race condition // need to retry to prevent a nasty race condition
lock(asyncCompletionQueue) lock(asyncCompletionQueue)
{ {
if (asyncCompletionQueue.Count == 0) return; // another thread drained it; can exit if (asyncCompletionQueue.Count == 0) return; // another thread drained it; can exit
} }
Thread.Sleep(1); Thread.Sleep(1);
} }
int total = 0; int total = 0;
while (true) while (true)
{ {
ICompletable next; ICompletable next;
lock (asyncCompletionQueue) lock (asyncCompletionQueue)
{ {
next = asyncCompletionQueue.Count == 0 ? null next = asyncCompletionQueue.Count == 0 ? null
: asyncCompletionQueue.Dequeue(); : asyncCompletionQueue.Dequeue();
} }
if (next == null) if (next == null)
{ {
// give it a moment and try again, noting that we might lose the battle // give it a moment and try again, noting that we might lose the battle
// when we pause // when we pause
Interlocked.CompareExchange(ref activeAsyncWorkerThread, 0, currentThread); Interlocked.CompareExchange(ref activeAsyncWorkerThread, 0, currentThread);
if (SpinWait() && Interlocked.CompareExchange(ref activeAsyncWorkerThread, currentThread, 0) == 0) if (SpinWait() && Interlocked.CompareExchange(ref activeAsyncWorkerThread, currentThread, 0) == 0)
{ {
// we paused, and we got the lock back; anything else? // we paused, and we got the lock back; anything else?
lock (asyncCompletionQueue) lock (asyncCompletionQueue)
{ {
next = asyncCompletionQueue.Count == 0 ? null next = asyncCompletionQueue.Count == 0 ? null
: asyncCompletionQueue.Dequeue(); : asyncCompletionQueue.Dequeue();
} }
} }
} }
if (next == null) break; // nothing to do <===== exit point if (next == null) break; // nothing to do <===== exit point
try try
{ {
multiplexer.Trace("Completing async (ordered): " + next, name); multiplexer.Trace("Completing async (ordered): " + next, name);
next.TryComplete(true); next.TryComplete(true);
Interlocked.Increment(ref completedAsync); Interlocked.Increment(ref completedAsync);
} }
catch (Exception ex) catch (Exception ex)
{ {
multiplexer.Trace("Async completion error: " + ex.Message, name); multiplexer.Trace("Async completion error: " + ex.Message, name);
Interlocked.Increment(ref failedAsync); Interlocked.Increment(ref failedAsync);
} }
total++; total++;
} }
multiplexer.Trace("Async completion worker processed " + total + " operations", name); multiplexer.Trace("Async completion worker processed " + total + " operations", name);
} }
finally finally
{ {
Interlocked.CompareExchange(ref activeAsyncWorkerThread, 0, currentThread); Interlocked.CompareExchange(ref activeAsyncWorkerThread, 0, currentThread);
} }
} }
private bool SpinWait() private bool SpinWait()
{ {
var sw = new SpinWait(); var sw = new SpinWait();
byte maxSpins = 128; byte maxSpins = 128;
do do
{ {
if (sw.NextSpinWillYield) if (sw.NextSpinWillYield)
return true; return true;
maxSpins--; maxSpins--;
} }
while (maxSpins > 0); while (maxSpins > 0);
return false; return false;
} }
} }
} }
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
/// <summary> /// <summary>
/// A collection of IProfiledCommands. /// A collection of IProfiledCommands.
/// ///
/// This is a very light weight data structure, only supporting enumeration. /// This is a very light weight data structure, only supporting enumeration.
/// ///
/// While it implements IEnumerable, it there are fewer allocations if one uses /// While it implements IEnumerable, it there are fewer allocations if one uses
/// it's explicit GetEnumerator() method. Using `foreach` does this automatically. /// it's explicit GetEnumerator() method. Using `foreach` does this automatically.
/// ///
/// This type is not threadsafe. /// This type is not threadsafe.
/// </summary> /// </summary>
public struct ProfiledCommandEnumerable : IEnumerable<IProfiledCommand> public struct ProfiledCommandEnumerable : IEnumerable<IProfiledCommand>
{ {
/// <summary> /// <summary>
/// Implements IEnumerator for ProfiledCommandEnumerable. /// Implements IEnumerator for ProfiledCommandEnumerable.
/// This implementation is comparable to List.Enumerator and Dictionary.Enumerator, /// This implementation is comparable to List.Enumerator and Dictionary.Enumerator,
/// and is provided to reduce allocations in the common (ie. foreach) case. /// and is provided to reduce allocations in the common (ie. foreach) case.
/// ///
/// This type is not threadsafe. /// This type is not threadsafe.
/// </summary> /// </summary>
public struct Enumerator : IEnumerator<IProfiledCommand> public struct Enumerator : IEnumerator<IProfiledCommand>
{ {
private ProfileStorage Head; private ProfileStorage Head;
private ProfileStorage CurrentBacker; private ProfileStorage CurrentBacker;
private bool IsEmpty => Head == null; private bool IsEmpty => Head == null;
private bool IsUnstartedOrFinished => CurrentBacker == null; private bool IsUnstartedOrFinished => CurrentBacker == null;
internal Enumerator(ProfileStorage head) internal Enumerator(ProfileStorage head)
{ {
Head = head; Head = head;
CurrentBacker = null; CurrentBacker = null;
} }
/// <summary> /// <summary>
/// The current element. /// The current element.
/// </summary> /// </summary>
public IProfiledCommand Current => CurrentBacker; public IProfiledCommand Current => CurrentBacker;
object System.Collections.IEnumerator.Current => CurrentBacker; object System.Collections.IEnumerator.Current => CurrentBacker;
/// <summary> /// <summary>
/// Advances the enumeration, returning true if there is a new element to consume and false /// Advances the enumeration, returning true if there is a new element to consume and false
/// if enumeration is complete. /// if enumeration is complete.
/// </summary> /// </summary>
public bool MoveNext() public bool MoveNext()
{ {
if (IsEmpty) return false; if (IsEmpty) return false;
if (IsUnstartedOrFinished) if (IsUnstartedOrFinished)
{ {
CurrentBacker = Head; CurrentBacker = Head;
} }
else else
{ {
CurrentBacker = CurrentBacker.NextElement; CurrentBacker = CurrentBacker.NextElement;
} }
return CurrentBacker != null; return CurrentBacker != null;
} }
/// <summary> /// <summary>
/// Resets the enumeration. /// Resets the enumeration.
/// </summary> /// </summary>
public void Reset() public void Reset()
{ {
CurrentBacker = null; CurrentBacker = null;
} }
/// <summary> /// <summary>
/// Disposes the enumeration. /// Disposes the enumeration.
/// subsequent attempts to enumerate results in undefined behavior. /// subsequent attempts to enumerate results in undefined behavior.
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
CurrentBacker = Head = null; CurrentBacker = Head = null;
} }
} }
private readonly ProfileStorage Head; private readonly ProfileStorage Head;
internal ProfiledCommandEnumerable(ProfileStorage head) internal ProfiledCommandEnumerable(ProfileStorage head)
{ {
Head = head; Head = head;
} }
/// <summary> /// <summary>
/// Returns an implementor of IEnumerator that, provided it isn't accessed /// Returns an implementor of IEnumerator that, provided it isn't accessed
/// though an interface, avoids allocations. /// though an interface, avoids allocations.
/// ///
/// `foreach` will automatically use this method. /// `foreach` will automatically use this method.
/// </summary> /// </summary>
public Enumerator GetEnumerator() => new Enumerator(Head); public Enumerator GetEnumerator() => new Enumerator(Head);
IEnumerator<IProfiledCommand> IEnumerable<IProfiledCommand>.GetEnumerator() => GetEnumerator(); IEnumerator<IProfiledCommand> IEnumerable<IProfiledCommand>.GetEnumerator() => GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
} }
/// <summary> /// <summary>
/// A thread-safe collection tailored to the "always append, with high contention, then enumerate once with no contention" /// A thread-safe collection tailored to the "always append, with high contention, then enumerate once with no contention"
/// behavior of our profiling. /// behavior of our profiling.
/// ///
/// Performs better than ConcurrentBag, which is important since profiling code shouldn't impact timings. /// Performs better than ConcurrentBag, which is important since profiling code shouldn't impact timings.
/// </summary> /// </summary>
internal sealed class ConcurrentProfileStorageCollection internal sealed class ConcurrentProfileStorageCollection
{ {
// internal for test purposes // internal for test purposes
internal static int AllocationCount = 0; internal static int AllocationCount = 0;
// It is, by definition, impossible for an element to be in 2 intrusive collections // It is, by definition, impossible for an element to be in 2 intrusive collections
// and we force Enumeration to release any reference to the collection object // and we force Enumeration to release any reference to the collection object
// so we can **always** pool these. // so we can **always** pool these.
private const int PoolSize = 64; private const int PoolSize = 64;
private static readonly ConcurrentProfileStorageCollection[] Pool = new ConcurrentProfileStorageCollection[PoolSize]; private static readonly ConcurrentProfileStorageCollection[] Pool = new ConcurrentProfileStorageCollection[PoolSize];
private volatile ProfileStorage Head; private volatile ProfileStorage Head;
private ConcurrentProfileStorageCollection() { } private ConcurrentProfileStorageCollection() { }
// for testing purposes only // for testing purposes only
internal static int CountInPool() internal static int CountInPool()
{ {
var ret = 0; var ret = 0;
for (var i = 0; i < PoolSize; i++) for (var i = 0; i < PoolSize; i++)
{ {
var inPool = Pool[i]; var inPool = Pool[i];
if (inPool != null) ret++; if (inPool != null) ret++;
} }
return ret; return ret;
} }
/// <summary> /// <summary>
/// This method is thread-safe. /// This method is thread-safe.
/// ///
/// Adds an element to the bag. /// Adds an element to the bag.
/// ///
/// Order is not preserved. /// Order is not preserved.
/// ///
/// The element can only be a member of *one* bag. /// The element can only be a member of *one* bag.
/// </summary> /// </summary>
/// <param name="command">The command to add.</param> /// <param name="command">The command to add.</param>
public void Add(ProfileStorage command) public void Add(ProfileStorage command)
{ {
while (true) while (true)
{ {
var cur = Head; var cur = Head;
command.NextElement = cur; command.NextElement = cur;
// Interlocked references to volatile fields are perfectly cromulent // Interlocked references to volatile fields are perfectly cromulent
#pragma warning disable 420 #pragma warning disable 420
var got = Interlocked.CompareExchange(ref Head, command, cur); var got = Interlocked.CompareExchange(ref Head, command, cur);
#pragma warning restore 420 #pragma warning restore 420
if (object.ReferenceEquals(got, cur)) break; if (object.ReferenceEquals(got, cur)) break;
} }
} }
/// <summary> /// <summary>
/// This method returns an enumerable view of the bag, and returns it to /// This method returns an enumerable view of the bag, and returns it to
/// an internal pool for reuse by GetOrCreate(). /// an internal pool for reuse by GetOrCreate().
/// ///
/// It is not thread safe. /// It is not thread safe.
/// ///
/// It should only be called once the bag is finished being mutated. /// It should only be called once the bag is finished being mutated.
/// </summary> /// </summary>
public ProfiledCommandEnumerable EnumerateAndReturnForReuse() public ProfiledCommandEnumerable EnumerateAndReturnForReuse()
{ {
var ret = new ProfiledCommandEnumerable(Head); var ret = new ProfiledCommandEnumerable(Head);
ReturnForReuse(); ReturnForReuse();
return ret; return ret;
} }
/// <summary> /// <summary>
/// This returns the ConcurrentProfileStorageCollection to an internal pool for reuse by GetOrCreate(). /// This returns the ConcurrentProfileStorageCollection to an internal pool for reuse by GetOrCreate().
/// </summary> /// </summary>
public void ReturnForReuse() public void ReturnForReuse()
{ {
// no need for interlocking, this isn't a thread safe method // no need for interlocking, this isn't a thread safe method
Head = null; Head = null;
for (var i = 0; i < PoolSize; i++) for (var i = 0; i < PoolSize; i++)
{ {
if (Interlocked.CompareExchange(ref Pool[i], this, null) == null) break; if (Interlocked.CompareExchange(ref Pool[i], this, null) == null) break;
} }
} }
/// <summary> /// <summary>
/// Returns a ConcurrentProfileStorageCollection to use. /// Returns a ConcurrentProfileStorageCollection to use.
/// ///
/// It *may* have allocated a new one, or it may return one that has previously been released. /// It *may* have allocated a new one, or it may return one that has previously been released.
/// To return the collection, call EnumerateAndReturnForReuse() /// To return the collection, call EnumerateAndReturnForReuse()
/// </summary> /// </summary>
public static ConcurrentProfileStorageCollection GetOrCreate() public static ConcurrentProfileStorageCollection GetOrCreate()
{ {
ConcurrentProfileStorageCollection found; ConcurrentProfileStorageCollection found;
for (int i = 0; i < PoolSize; i++) for (int i = 0; i < PoolSize; i++)
{ {
if ((found = Interlocked.Exchange(ref Pool[i], null)) != null) if ((found = Interlocked.Exchange(ref Pool[i], null)) != null)
{ {
return found; return found;
} }
} }
Interlocked.Increment(ref AllocationCount); Interlocked.Increment(ref AllocationCount);
return new ConcurrentProfileStorageCollection(); return new ConcurrentProfileStorageCollection();
} }
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Security.Authentication; using System.Security.Authentication;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
/// <summary> /// <summary>
/// The options relevant to a set of redis connections /// The options relevant to a set of redis connections
/// </summary> /// </summary>
public sealed class ConfigurationOptions public sealed class ConfigurationOptions
#if !NETSTANDARD1_5 #if !NETSTANDARD1_5
: ICloneable : ICloneable
#endif #endif
{ {
internal const string DefaultTieBreaker = "__Booksleeve_TieBreak", DefaultConfigurationChannel = "__Booksleeve_MasterChanged"; internal const string DefaultTieBreaker = "__Booksleeve_TieBreak", DefaultConfigurationChannel = "__Booksleeve_MasterChanged";
private static class OptionKeys private static class OptionKeys
{ {
public static int ParseInt32(string key, string value, int minValue = int.MinValue, int maxValue = int.MaxValue) public static int ParseInt32(string key, string value, int minValue = int.MinValue, int maxValue = int.MaxValue)
{ {
if (!Format.TryParseInt32(value, out int tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires an integer value"); if (!Format.TryParseInt32(value, out int tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires an integer value");
if (tmp < minValue) throw new ArgumentOutOfRangeException("Keyword '" + key + "' has a minimum value of " + minValue); if (tmp < minValue) throw new ArgumentOutOfRangeException("Keyword '" + key + "' has a minimum value of " + minValue);
if (tmp > maxValue) throw new ArgumentOutOfRangeException("Keyword '" + key + "' has a maximum value of " + maxValue); if (tmp > maxValue) throw new ArgumentOutOfRangeException("Keyword '" + key + "' has a maximum value of " + maxValue);
return tmp; return tmp;
} }
internal static bool ParseBoolean(string key, string value) internal static bool ParseBoolean(string key, string value)
{ {
if (!Format.TryParseBoolean(value, out bool tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires a boolean value"); if (!Format.TryParseBoolean(value, out bool tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires a boolean value");
return tmp; return tmp;
} }
internal static Version ParseVersion(string key, string value) internal static Version ParseVersion(string key, string value)
{ {
if (!System.Version.TryParse(value, out Version tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires a version value"); if (!System.Version.TryParse(value, out Version tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires a version value");
return tmp; return tmp;
} }
internal static Proxy ParseProxy(string key, string value) internal static Proxy ParseProxy(string key, string value)
{ {
if (!Enum.TryParse(value, true, out Proxy tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires a proxy value"); if (!Enum.TryParse(value, true, out Proxy tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires a proxy value");
return tmp; return tmp;
} }
internal static SslProtocols ParseSslProtocols(string key, string value) internal static SslProtocols ParseSslProtocols(string key, string value)
{ {
//Flags expect commas as separators, but we need to use '|' since commas are already used in the connection string to mean something else //Flags expect commas as separators, but we need to use '|' since commas are already used in the connection string to mean something else
value = value?.Replace("|", ","); value = value?.Replace("|", ",");
if (!Enum.TryParse(value, true, out SslProtocols tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires an SslProtocol value (multiple values separated by '|')."); if (!Enum.TryParse(value, true, out SslProtocols tmp)) throw new ArgumentOutOfRangeException("Keyword '" + key + "' requires an SslProtocol value (multiple values separated by '|').");
return tmp; return tmp;
} }
internal static void Unknown(string key) internal static void Unknown(string key)
{ {
throw new ArgumentException("Keyword '" + key + "' is not supported"); throw new ArgumentException("Keyword '" + key + "' is not supported");
} }
internal const string internal const string
AbortOnConnectFail = "abortConnect", AbortOnConnectFail = "abortConnect",
AllowAdmin = "allowAdmin", AllowAdmin = "allowAdmin",
ChannelPrefix = "channelPrefix", ChannelPrefix = "channelPrefix",
ConfigChannel = "configChannel", ConfigChannel = "configChannel",
ConfigCheckSeconds = "configCheckSeconds", ConfigCheckSeconds = "configCheckSeconds",
ConnectRetry = "connectRetry", ConnectRetry = "connectRetry",
ConnectTimeout = "connectTimeout", ConnectTimeout = "connectTimeout",
DefaultDatabase = "defaultDatabase", DefaultDatabase = "defaultDatabase",
HighPrioritySocketThreads = "highPriorityThreads", HighPrioritySocketThreads = "highPriorityThreads",
KeepAlive = "keepAlive", KeepAlive = "keepAlive",
ClientName = "name", ClientName = "name",
Password = "password", Password = "password",
PreserveAsyncOrder = "preserveAsyncOrder", PreserveAsyncOrder = "preserveAsyncOrder",
Proxy = "proxy", Proxy = "proxy",
ResolveDns = "resolveDns", ResolveDns = "resolveDns",
ResponseTimeout = "responseTimeout", ResponseTimeout = "responseTimeout",
ServiceName = "serviceName", ServiceName = "serviceName",
Ssl = "ssl", Ssl = "ssl",
SslHost = "sslHost", SslHost = "sslHost",
SslProtocols = "sslProtocols", SslProtocols = "sslProtocols",
SyncTimeout = "syncTimeout", SyncTimeout = "syncTimeout",
TieBreaker = "tiebreaker", TieBreaker = "tiebreaker",
Version = "version", Version = "version",
WriteBuffer = "writeBuffer"; WriteBuffer = "writeBuffer";
private static readonly Dictionary<string, string> normalizedOptions = new[] private static readonly Dictionary<string, string> normalizedOptions = new[]
{ {
AbortOnConnectFail, AbortOnConnectFail,
AllowAdmin, AllowAdmin,
ChannelPrefix, ChannelPrefix,
ClientName, ClientName,
ConfigChannel, ConfigChannel,
ConfigCheckSeconds, ConfigCheckSeconds,
ConnectRetry, ConnectRetry,
ConnectTimeout, ConnectTimeout,
DefaultDatabase, DefaultDatabase,
HighPrioritySocketThreads, HighPrioritySocketThreads,
KeepAlive, KeepAlive,
Password, Password,
PreserveAsyncOrder, PreserveAsyncOrder,
Proxy, Proxy,
ResolveDns, ResolveDns,
ServiceName, ServiceName,
Ssl, Ssl,
SslHost, SslHost,
SslProtocols, SslProtocols,
SyncTimeout, SyncTimeout,
TieBreaker, TieBreaker,
Version, Version,
WriteBuffer, WriteBuffer,
}.ToDictionary(x => x, StringComparer.OrdinalIgnoreCase); }.ToDictionary(x => x, StringComparer.OrdinalIgnoreCase);
public static string TryNormalize(string value) public static string TryNormalize(string value)
{ {
if (value != null && normalizedOptions.TryGetValue(value, out string tmp)) if (value != null && normalizedOptions.TryGetValue(value, out string tmp))
{ {
return tmp ?? ""; return tmp ?? "";
} }
return value ?? ""; return value ?? "";
} }
} }
private bool? allowAdmin, abortOnConnectFail, highPrioritySocketThreads, resolveDns, ssl, preserveAsyncOrder; private bool? allowAdmin, abortOnConnectFail, highPrioritySocketThreads, resolveDns, ssl, preserveAsyncOrder;
private string tieBreaker, sslHost, configChannel; private string tieBreaker, sslHost, configChannel;
private CommandMap commandMap; private CommandMap commandMap;
private Version defaultVersion; private Version defaultVersion;
private int? keepAlive, syncTimeout, connectTimeout, responseTimeout, writeBuffer, connectRetry, configCheckSeconds; private int? keepAlive, syncTimeout, connectTimeout, responseTimeout, writeBuffer, connectRetry, configCheckSeconds;
private Proxy? proxy; private Proxy? proxy;
private IReconnectRetryPolicy reconnectRetryPolicy; private IReconnectRetryPolicy reconnectRetryPolicy;
/// <summary> /// <summary>
/// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication; note /// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication; note
/// that this cannot be specified in the configuration-string. /// that this cannot be specified in the configuration-string.
/// </summary> /// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")]
public event LocalCertificateSelectionCallback CertificateSelection; public event LocalCertificateSelectionCallback CertificateSelection;
/// <summary> /// <summary>
/// A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party; note /// A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party; note
/// that this cannot be specified in the configuration-string. /// that this cannot be specified in the configuration-string.
/// </summary> /// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")]
public event RemoteCertificateValidationCallback CertificateValidation; public event RemoteCertificateValidationCallback CertificateValidation;
/// <summary> /// <summary>
/// Gets or sets whether connect/configuration timeouts should be explicitly notified via a TimeoutException /// Gets or sets whether connect/configuration timeouts should be explicitly notified via a TimeoutException
/// </summary> /// </summary>
public bool AbortOnConnectFail { get { return abortOnConnectFail ?? GetDefaultAbortOnConnectFailSetting(); } set { abortOnConnectFail = value; } } public bool AbortOnConnectFail { get { return abortOnConnectFail ?? GetDefaultAbortOnConnectFailSetting(); } set { abortOnConnectFail = value; } }
/// <summary> /// <summary>
/// Indicates whether admin operations should be allowed /// Indicates whether admin operations should be allowed
/// </summary> /// </summary>
public bool AllowAdmin { get { return allowAdmin.GetValueOrDefault(); } set { allowAdmin = value; } } public bool AllowAdmin { get { return allowAdmin.GetValueOrDefault(); } set { allowAdmin = value; } }
/// <summary> /// <summary>
/// Indicates whether the connection should be encrypted /// Indicates whether the connection should be encrypted
/// </summary> /// </summary>
[Obsolete("Please use .Ssl instead of .UseSsl"), [Obsolete("Please use .Ssl instead of .UseSsl"),
Browsable(false), Browsable(false),
EditorBrowsable(EditorBrowsableState.Never)] EditorBrowsable(EditorBrowsableState.Never)]
public bool UseSsl { get { return Ssl; } set { Ssl = value; } } public bool UseSsl { get { return Ssl; } set { Ssl = value; } }
/// <summary> /// <summary>
/// Automatically encodes and decodes channels /// Automatically encodes and decodes channels
/// </summary> /// </summary>
public RedisChannel ChannelPrefix { get; set; } public RedisChannel ChannelPrefix { get; set; }
/// <summary> /// <summary>
/// The client name to use for all connections /// The client name to use for all connections
/// </summary> /// </summary>
public string ClientName { get; set; } public string ClientName { get; set; }
/// <summary> /// <summary>
/// The number of times to repeat the initial connect cycle if no servers respond promptly /// The number of times to repeat the initial connect cycle if no servers respond promptly
/// </summary> /// </summary>
public int ConnectRetry { get { return connectRetry ?? 3; } set { connectRetry = value; } } public int ConnectRetry { get { return connectRetry ?? 3; } set { connectRetry = value; } }
/// <summary> /// <summary>
/// The command-map associated with this configuration /// The command-map associated with this configuration
/// </summary> /// </summary>
public CommandMap CommandMap public CommandMap CommandMap
{ {
get get
{ {
if (commandMap != null) return commandMap; if (commandMap != null) return commandMap;
switch (Proxy) switch (Proxy)
{ {
case Proxy.Twemproxy: case Proxy.Twemproxy:
return CommandMap.Twemproxy; return CommandMap.Twemproxy;
default: default:
return CommandMap.Default; return CommandMap.Default;
} }
} }
set set
{ {
commandMap = value ?? throw new ArgumentNullException(nameof(value)); commandMap = value ?? throw new ArgumentNullException(nameof(value));
} }
} }
/// <summary> /// <summary>
/// Channel to use for broadcasting and listening for configuration change notification /// Channel to use for broadcasting and listening for configuration change notification
/// </summary> /// </summary>
public string ConfigurationChannel { get { return configChannel ?? DefaultConfigurationChannel; } set { configChannel = value; } } public string ConfigurationChannel { get { return configChannel ?? DefaultConfigurationChannel; } set { configChannel = value; } }
/// <summary> /// <summary>
/// Specifies the time in milliseconds that should be allowed for connection (defaults to 5 seconds unless SyncTimeout is higher) /// Specifies the time in milliseconds that should be allowed for connection (defaults to 5 seconds unless SyncTimeout is higher)
/// </summary> /// </summary>
public int ConnectTimeout public int ConnectTimeout
{ {
get get
{ {
if (connectTimeout.HasValue) return connectTimeout.GetValueOrDefault(); if (connectTimeout.HasValue) return connectTimeout.GetValueOrDefault();
return Math.Max(5000, SyncTimeout); return Math.Max(5000, SyncTimeout);
} }
set { connectTimeout = value; } set { connectTimeout = value; }
} }
/// <summary> /// <summary>
/// Specifies the default database to be used when calling ConnectionMultiplexer.GetDatabase() without any parameters /// Specifies the default database to be used when calling ConnectionMultiplexer.GetDatabase() without any parameters
/// </summary> /// </summary>
public int? DefaultDatabase { get; set; } public int? DefaultDatabase { get; set; }
/// <summary> /// <summary>
/// The server version to assume /// The server version to assume
/// </summary> /// </summary>
public Version DefaultVersion { get { return defaultVersion ?? (IsAzureEndpoint() ? RedisFeatures.v3_0_0 : RedisFeatures.v2_0_0); } set { defaultVersion = value; } } public Version DefaultVersion { get { return defaultVersion ?? (IsAzureEndpoint() ? RedisFeatures.v3_0_0 : RedisFeatures.v2_0_0); } set { defaultVersion = value; } }
/// <summary> /// <summary>
/// The endpoints defined for this configuration /// The endpoints defined for this configuration
/// </summary> /// </summary>
public EndPointCollection EndPoints { get; } = new EndPointCollection(); public EndPointCollection EndPoints { get; } = new EndPointCollection();
/// <summary> /// <summary>
/// Use ThreadPriority.AboveNormal for SocketManager reader and writer threads (true by default). If false, ThreadPriority.Normal will be used. /// Use ThreadPriority.AboveNormal for SocketManager reader and writer threads (true by default). If false, ThreadPriority.Normal will be used.
/// </summary> /// </summary>
public bool HighPrioritySocketThreads { get { return highPrioritySocketThreads ?? true; } set { highPrioritySocketThreads = value; } } public bool HighPrioritySocketThreads { get { return highPrioritySocketThreads ?? true; } set { highPrioritySocketThreads = value; } }
/// <summary> /// <summary>
/// Specifies the time in seconds at which connections should be pinged to ensure validity /// Specifies the time in seconds at which connections should be pinged to ensure validity
/// </summary> /// </summary>
public int KeepAlive { get { return keepAlive.GetValueOrDefault(-1); } set { keepAlive = value; } } public int KeepAlive { get { return keepAlive.GetValueOrDefault(-1); } set { keepAlive = value; } }
/// <summary> /// <summary>
/// The password to use to authenticate with the server. /// The password to use to authenticate with the server.
/// </summary> /// </summary>
public string Password { get; set; } public string Password { get; set; }
/// <summary> /// <summary>
/// Specifies whether asynchronous operations should be invoked in a way that guarantees their original delivery order /// Specifies whether asynchronous operations should be invoked in a way that guarantees their original delivery order
/// </summary> /// </summary>
public bool PreserveAsyncOrder { get { return preserveAsyncOrder.GetValueOrDefault(true); } set { preserveAsyncOrder = value; } } public bool PreserveAsyncOrder { get { return preserveAsyncOrder.GetValueOrDefault(true); } set { preserveAsyncOrder = value; } }
/// <summary> /// <summary>
/// Type of proxy to use (if any); for example Proxy.Twemproxy. /// Type of proxy to use (if any); for example Proxy.Twemproxy.
/// </summary> /// </summary>
public Proxy Proxy { get { return proxy.GetValueOrDefault(); } set { proxy = value; } } public Proxy Proxy { get { return proxy.GetValueOrDefault(); } set { proxy = value; } }
/// <summary> /// <summary>
/// The retry policy to be used for connection reconnects /// The retry policy to be used for connection reconnects
/// </summary> /// </summary>
public IReconnectRetryPolicy ReconnectRetryPolicy { get { return reconnectRetryPolicy ?? (reconnectRetryPolicy = new LinearRetry(ConnectTimeout)); } set { reconnectRetryPolicy = value; } } public IReconnectRetryPolicy ReconnectRetryPolicy { get { return reconnectRetryPolicy ?? (reconnectRetryPolicy = new LinearRetry(ConnectTimeout)); } set { reconnectRetryPolicy = value; } }
/// <summary> /// <summary>
/// Indicates whether endpoints should be resolved via DNS before connecting. /// Indicates whether endpoints should be resolved via DNS before connecting.
/// If enabled the ConnectionMultiplexer will re-resolve DNS /// If enabled the ConnectionMultiplexer will re-resolve DNS
/// when attempting to re-connect after a connection failure. /// when attempting to re-connect after a connection failure.
/// </summary> /// </summary>
public bool ResolveDns { get { return resolveDns.GetValueOrDefault(); } set { resolveDns = value; } } public bool ResolveDns { get { return resolveDns.GetValueOrDefault(); } set { resolveDns = value; } }
/// <summary> /// <summary>
/// Specifies the time in milliseconds that the system should allow for responses before concluding that the socket is unhealthy /// Specifies the time in milliseconds that the system should allow for responses before concluding that the socket is unhealthy
/// (defaults to SyncTimeout) /// (defaults to SyncTimeout)
/// </summary> /// </summary>
public int ResponseTimeout { get { return responseTimeout ?? SyncTimeout; } set { responseTimeout = value; } } public int ResponseTimeout { get { return responseTimeout ?? SyncTimeout; } set { responseTimeout = value; } }
/// <summary> /// <summary>
/// The service name used to resolve a service via sentinel. /// The service name used to resolve a service via sentinel.
/// </summary> /// </summary>
public string ServiceName { get; set; } public string ServiceName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the SocketManager instance to be used with these options; if this is null a per-multiplexer /// Gets or sets the SocketManager instance to be used with these options; if this is null a per-multiplexer
/// SocketManager is created automatically. /// SocketManager is created automatically.
/// </summary> /// </summary>
public SocketManager SocketManager { get; set; } public SocketManager SocketManager { get; set; }
/// <summary> /// <summary>
/// Indicates whether the connection should be encrypted /// Indicates whether the connection should be encrypted
/// </summary> /// </summary>
public bool Ssl { get { return ssl.GetValueOrDefault(); } set { ssl = value; } } public bool Ssl { get { return ssl.GetValueOrDefault(); } set { ssl = value; } }
/// <summary> /// <summary>
/// The target-host to use when validating SSL certificate; setting a value here enables SSL mode /// The target-host to use when validating SSL certificate; setting a value here enables SSL mode
/// </summary> /// </summary>
public string SslHost { get { return sslHost ?? InferSslHostFromEndpoints(); } set { sslHost = value; } } public string SslHost { get { return sslHost ?? InferSslHostFromEndpoints(); } set { sslHost = value; } }
/// <summary> /// <summary>
/// Configures which Ssl/TLS protocols should be allowed. If not set, defaults are chosen by the .NET framework. /// Configures which Ssl/TLS protocols should be allowed. If not set, defaults are chosen by the .NET framework.
/// </summary> /// </summary>
public SslProtocols? SslProtocols { get; set; } public SslProtocols? SslProtocols { get; set; }
/// <summary> /// <summary>
/// Specifies the time in milliseconds that the system should allow for synchronous operations (defaults to 1 second) /// Specifies the time in milliseconds that the system should allow for synchronous operations (defaults to 1 second)
/// </summary> /// </summary>
public int SyncTimeout { get { return syncTimeout.GetValueOrDefault(1000); } set { syncTimeout = value; } } public int SyncTimeout { get { return syncTimeout.GetValueOrDefault(1000); } set { syncTimeout = value; } }
/// <summary> /// <summary>
/// Tie-breaker used to choose between masters (must match the endpoint exactly) /// Tie-breaker used to choose between masters (must match the endpoint exactly)
/// </summary> /// </summary>
public string TieBreaker { get { return tieBreaker ?? DefaultTieBreaker; } set { tieBreaker = value; } } public string TieBreaker { get { return tieBreaker ?? DefaultTieBreaker; } set { tieBreaker = value; } }
/// <summary> /// <summary>
/// The size of the output buffer to use /// The size of the output buffer to use
/// </summary> /// </summary>
public int WriteBuffer { get { return writeBuffer.GetValueOrDefault(4096); } set { writeBuffer = value; } } public int WriteBuffer { get { return writeBuffer.GetValueOrDefault(4096); } set { writeBuffer = value; } }
internal LocalCertificateSelectionCallback CertificateSelectionCallback { get { return CertificateSelection; } private set { CertificateSelection = value; } } internal LocalCertificateSelectionCallback CertificateSelectionCallback { get { return CertificateSelection; } private set { CertificateSelection = value; } }
// these just rip out the underlying handlers, bypassing the event accessors - needed when creating the SSL stream // these just rip out the underlying handlers, bypassing the event accessors - needed when creating the SSL stream
internal RemoteCertificateValidationCallback CertificateValidationCallback { get { return CertificateValidation; } private set { CertificateValidation = value; } } internal RemoteCertificateValidationCallback CertificateValidationCallback { get { return CertificateValidation; } private set { CertificateValidation = value; } }
/// <summary> /// <summary>
/// Check configuration every n seconds (every minute by default) /// Check configuration every n seconds (every minute by default)
/// </summary> /// </summary>
public int ConfigCheckSeconds { get { return configCheckSeconds.GetValueOrDefault(60); } set { configCheckSeconds = value; } } public int ConfigCheckSeconds { get { return configCheckSeconds.GetValueOrDefault(60); } set { configCheckSeconds = value; } }
/// <summary> /// <summary>
/// Parse the configuration from a comma-delimited configuration string /// Parse the configuration from a comma-delimited configuration string
/// </summary> /// </summary>
/// <param name="configuration">The configuration string to parse.</param> /// <param name="configuration">The configuration string to parse.</param>
/// <exception cref="ArgumentNullException"><paramref name="configuration"/> is <c>null</c>.</exception> /// <exception cref="ArgumentNullException"><paramref name="configuration"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException"><paramref name="configuration"/> is empty.</exception> /// <exception cref="ArgumentException"><paramref name="configuration"/> is empty.</exception>
public static ConfigurationOptions Parse(string configuration) public static ConfigurationOptions Parse(string configuration)
{ {
var options = new ConfigurationOptions(); var options = new ConfigurationOptions();
options.DoParse(configuration, false); options.DoParse(configuration, false);
return options; return options;
} }
/// <summary> /// <summary>
/// Parse the configuration from a comma-delimited configuration string /// Parse the configuration from a comma-delimited configuration string
/// </summary> /// </summary>
/// <param name="configuration">The configuration string to parse.</param> /// <param name="configuration">The configuration string to parse.</param>
/// <param name="ignoreUnknown">Whether to ignore unknown elements in <paramref name="configuration"/>.</param> /// <param name="ignoreUnknown">Whether to ignore unknown elements in <paramref name="configuration"/>.</param>
/// <exception cref="ArgumentNullException"><paramref name="configuration"/> is <c>null</c>.</exception> /// <exception cref="ArgumentNullException"><paramref name="configuration"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException"><paramref name="configuration"/> is empty.</exception> /// <exception cref="ArgumentException"><paramref name="configuration"/> is empty.</exception>
public static ConfigurationOptions Parse(string configuration, bool ignoreUnknown) public static ConfigurationOptions Parse(string configuration, bool ignoreUnknown)
{ {
var options = new ConfigurationOptions(); var options = new ConfigurationOptions();
options.DoParse(configuration, ignoreUnknown); options.DoParse(configuration, ignoreUnknown);
return options; return options;
} }
/// <summary> /// <summary>
/// Create a copy of the configuration /// Create a copy of the configuration
/// </summary> /// </summary>
public ConfigurationOptions Clone() public ConfigurationOptions Clone()
{ {
var options = new ConfigurationOptions var options = new ConfigurationOptions
{ {
ClientName = ClientName, ClientName = ClientName,
ServiceName = ServiceName, ServiceName = ServiceName,
keepAlive = keepAlive, keepAlive = keepAlive,
syncTimeout = syncTimeout, syncTimeout = syncTimeout,
allowAdmin = allowAdmin, allowAdmin = allowAdmin,
defaultVersion = defaultVersion, defaultVersion = defaultVersion,
connectTimeout = connectTimeout, connectTimeout = connectTimeout,
Password = Password, Password = Password,
tieBreaker = tieBreaker, tieBreaker = tieBreaker,
writeBuffer = writeBuffer, writeBuffer = writeBuffer,
ssl = ssl, ssl = ssl,
sslHost = sslHost, sslHost = sslHost,
highPrioritySocketThreads = highPrioritySocketThreads, highPrioritySocketThreads = highPrioritySocketThreads,
configChannel = configChannel, configChannel = configChannel,
abortOnConnectFail = abortOnConnectFail, abortOnConnectFail = abortOnConnectFail,
resolveDns = resolveDns, resolveDns = resolveDns,
proxy = proxy, proxy = proxy,
commandMap = commandMap, commandMap = commandMap,
CertificateValidationCallback = CertificateValidationCallback, CertificateValidationCallback = CertificateValidationCallback,
CertificateSelectionCallback = CertificateSelectionCallback, CertificateSelectionCallback = CertificateSelectionCallback,
ChannelPrefix = ChannelPrefix.Clone(), ChannelPrefix = ChannelPrefix.Clone(),
SocketManager = SocketManager, SocketManager = SocketManager,
connectRetry = connectRetry, connectRetry = connectRetry,
configCheckSeconds = configCheckSeconds, configCheckSeconds = configCheckSeconds,
responseTimeout = responseTimeout, responseTimeout = responseTimeout,
DefaultDatabase = DefaultDatabase, DefaultDatabase = DefaultDatabase,
ReconnectRetryPolicy = reconnectRetryPolicy, ReconnectRetryPolicy = reconnectRetryPolicy,
preserveAsyncOrder = preserveAsyncOrder, preserveAsyncOrder = preserveAsyncOrder,
#if !NETSTANDARD1_5 #if !NETSTANDARD1_5
SslProtocols = SslProtocols, SslProtocols = SslProtocols,
#endif #endif
}; };
foreach (var item in EndPoints) foreach (var item in EndPoints)
options.EndPoints.Add(item); options.EndPoints.Add(item);
return options; return options;
} }
/// <summary> /// <summary>
/// Resolve the default port for any endpoints that did not have a port explicitly specified /// Resolve the default port for any endpoints that did not have a port explicitly specified
/// </summary> /// </summary>
public void SetDefaultPorts() public void SetDefaultPorts()
{ {
EndPoints.SetDefaultPorts(Ssl ? 6380 : 6379); EndPoints.SetDefaultPorts(Ssl ? 6380 : 6379);
} }
/// <summary> /// <summary>
/// Returns the effective configuration string for this configuration, including Redis credentials. /// Returns the effective configuration string for this configuration, including Redis credentials.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Includes password to allow generation of configuration strings used for connecting multiplexer. /// Includes password to allow generation of configuration strings used for connecting multiplexer.
/// </remarks> /// </remarks>
public override string ToString() => ToString(includePassword: true); public override string ToString() => ToString(includePassword: true);
/// <summary> /// <summary>
/// Returns the effective configuration string for this configuration /// Returns the effective configuration string for this configuration
/// with the option to include or exclude the password from the string. /// with the option to include or exclude the password from the string.
/// </summary> /// </summary>
/// <param name="includePassword">Whether to include the password.</param> /// <param name="includePassword">Whether to include the password.</param>
public string ToString(bool includePassword) public string ToString(bool includePassword)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var endpoint in EndPoints) foreach (var endpoint in EndPoints)
{ {
Append(sb, Format.ToString(endpoint)); Append(sb, Format.ToString(endpoint));
} }
Append(sb, OptionKeys.ClientName, ClientName); Append(sb, OptionKeys.ClientName, ClientName);
Append(sb, OptionKeys.ServiceName, ServiceName); Append(sb, OptionKeys.ServiceName, ServiceName);
Append(sb, OptionKeys.KeepAlive, keepAlive); Append(sb, OptionKeys.KeepAlive, keepAlive);
Append(sb, OptionKeys.SyncTimeout, syncTimeout); Append(sb, OptionKeys.SyncTimeout, syncTimeout);
Append(sb, OptionKeys.AllowAdmin, allowAdmin); Append(sb, OptionKeys.AllowAdmin, allowAdmin);
Append(sb, OptionKeys.Version, defaultVersion); Append(sb, OptionKeys.Version, defaultVersion);
Append(sb, OptionKeys.ConnectTimeout, connectTimeout); Append(sb, OptionKeys.ConnectTimeout, connectTimeout);
Append(sb, OptionKeys.Password, includePassword ? Password : "*****"); Append(sb, OptionKeys.Password, includePassword ? Password : "*****");
Append(sb, OptionKeys.TieBreaker, tieBreaker); Append(sb, OptionKeys.TieBreaker, tieBreaker);
Append(sb, OptionKeys.WriteBuffer, writeBuffer); Append(sb, OptionKeys.WriteBuffer, writeBuffer);
Append(sb, OptionKeys.Ssl, ssl); Append(sb, OptionKeys.Ssl, ssl);
Append(sb, OptionKeys.SslHost, sslHost); Append(sb, OptionKeys.SslHost, sslHost);
Append(sb, OptionKeys.HighPrioritySocketThreads, highPrioritySocketThreads); Append(sb, OptionKeys.HighPrioritySocketThreads, highPrioritySocketThreads);
Append(sb, OptionKeys.ConfigChannel, configChannel); Append(sb, OptionKeys.ConfigChannel, configChannel);
Append(sb, OptionKeys.AbortOnConnectFail, abortOnConnectFail); Append(sb, OptionKeys.AbortOnConnectFail, abortOnConnectFail);
Append(sb, OptionKeys.ResolveDns, resolveDns); Append(sb, OptionKeys.ResolveDns, resolveDns);
Append(sb, OptionKeys.ChannelPrefix, (string)ChannelPrefix); Append(sb, OptionKeys.ChannelPrefix, (string)ChannelPrefix);
Append(sb, OptionKeys.ConnectRetry, connectRetry); Append(sb, OptionKeys.ConnectRetry, connectRetry);
Append(sb, OptionKeys.Proxy, proxy); Append(sb, OptionKeys.Proxy, proxy);
Append(sb, OptionKeys.ConfigCheckSeconds, configCheckSeconds); Append(sb, OptionKeys.ConfigCheckSeconds, configCheckSeconds);
Append(sb, OptionKeys.ResponseTimeout, responseTimeout); Append(sb, OptionKeys.ResponseTimeout, responseTimeout);
Append(sb, OptionKeys.DefaultDatabase, DefaultDatabase); Append(sb, OptionKeys.DefaultDatabase, DefaultDatabase);
Append(sb, OptionKeys.PreserveAsyncOrder, preserveAsyncOrder); Append(sb, OptionKeys.PreserveAsyncOrder, preserveAsyncOrder);
commandMap?.AppendDeltas(sb); commandMap?.AppendDeltas(sb);
return sb.ToString(); return sb.ToString();
} }
internal bool HasDnsEndPoints() internal bool HasDnsEndPoints()
{ {
foreach (var endpoint in EndPoints) if (endpoint is DnsEndPoint) return true; foreach (var endpoint in EndPoints) if (endpoint is DnsEndPoint) return true;
return false; return false;
} }
internal async Task ResolveEndPointsAsync(ConnectionMultiplexer multiplexer, TextWriter log) internal async Task ResolveEndPointsAsync(ConnectionMultiplexer multiplexer, TextWriter log)
{ {
var cache = new Dictionary<string, IPAddress>(StringComparer.OrdinalIgnoreCase); var cache = new Dictionary<string, IPAddress>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < EndPoints.Count; i++) for (int i = 0; i < EndPoints.Count; i++)
{ {
if (EndPoints[i] is DnsEndPoint dns) if (EndPoints[i] is DnsEndPoint dns)
{ {
try try
{ {
if (dns.Host == ".") if (dns.Host == ".")
{ {
EndPoints[i] = new IPEndPoint(IPAddress.Loopback, dns.Port); EndPoints[i] = new IPEndPoint(IPAddress.Loopback, dns.Port);
} }
else if (cache.TryGetValue(dns.Host, out IPAddress ip)) else if (cache.TryGetValue(dns.Host, out IPAddress ip))
{ // use cache { // use cache
EndPoints[i] = new IPEndPoint(ip, dns.Port); EndPoints[i] = new IPEndPoint(ip, dns.Port);
} }
else else
{ {
multiplexer.LogLocked(log, "Using DNS to resolve '{0}'...", dns.Host); multiplexer.LogLocked(log, "Using DNS to resolve '{0}'...", dns.Host);
var ips = await Dns.GetHostAddressesAsync(dns.Host).ObserveErrors().ForAwait(); var ips = await Dns.GetHostAddressesAsync(dns.Host).ObserveErrors().ForAwait();
if (ips.Length == 1) if (ips.Length == 1)
{ {
ip = ips[0]; ip = ips[0];
multiplexer.LogLocked(log, "'{0}' => {1}", dns.Host, ip); multiplexer.LogLocked(log, "'{0}' => {1}", dns.Host, ip);
cache[dns.Host] = ip; cache[dns.Host] = ip;
EndPoints[i] = new IPEndPoint(ip, dns.Port); EndPoints[i] = new IPEndPoint(ip, dns.Port);
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
multiplexer.OnInternalError(ex); multiplexer.OnInternalError(ex);
multiplexer.LogLocked(log, ex.Message); multiplexer.LogLocked(log, ex.Message);
} }
} }
} }
} }
private static void Append(StringBuilder sb, object value) private static void Append(StringBuilder sb, object value)
{ {
if (value == null) return; if (value == null) return;
string s = Format.ToString(value); string s = Format.ToString(value);
if (!string.IsNullOrWhiteSpace(s)) if (!string.IsNullOrWhiteSpace(s))
{ {
if (sb.Length != 0) sb.Append(','); if (sb.Length != 0) sb.Append(',');
sb.Append(s); sb.Append(s);
} }
} }
private static void Append(StringBuilder sb, string prefix, object value) private static void Append(StringBuilder sb, string prefix, object value)
{ {
string s = value?.ToString(); string s = value?.ToString();
if (!string.IsNullOrWhiteSpace(s)) if (!string.IsNullOrWhiteSpace(s))
{ {
if (sb.Length != 0) sb.Append(','); if (sb.Length != 0) sb.Append(',');
if (!string.IsNullOrEmpty(prefix)) if (!string.IsNullOrEmpty(prefix))
{ {
sb.Append(prefix).Append('='); sb.Append(prefix).Append('=');
} }
sb.Append(s); sb.Append(s);
} }
} }
private void Clear() private void Clear()
{ {
ClientName = ServiceName = Password = tieBreaker = sslHost = configChannel = null; ClientName = ServiceName = Password = tieBreaker = sslHost = configChannel = null;
keepAlive = syncTimeout = connectTimeout = writeBuffer = connectRetry = configCheckSeconds = DefaultDatabase = null; keepAlive = syncTimeout = connectTimeout = writeBuffer = connectRetry = configCheckSeconds = DefaultDatabase = null;
allowAdmin = abortOnConnectFail = highPrioritySocketThreads = resolveDns = ssl = preserveAsyncOrder = null; allowAdmin = abortOnConnectFail = highPrioritySocketThreads = resolveDns = ssl = preserveAsyncOrder = null;
defaultVersion = null; defaultVersion = null;
EndPoints.Clear(); EndPoints.Clear();
commandMap = null; commandMap = null;
CertificateSelection = null; CertificateSelection = null;
CertificateValidation = null; CertificateValidation = null;
ChannelPrefix = default(RedisChannel); ChannelPrefix = default(RedisChannel);
SocketManager = null; SocketManager = null;
} }
#if !NETSTANDARD1_5 #if !NETSTANDARD1_5
object ICloneable.Clone() { return Clone(); } object ICloneable.Clone() { return Clone(); }
#endif #endif
private void DoParse(string configuration, bool ignoreUnknown) private void DoParse(string configuration, bool ignoreUnknown)
{ {
if (configuration == null) if (configuration == null)
{ {
throw new ArgumentNullException(nameof(configuration)); throw new ArgumentNullException(nameof(configuration));
} }
if (string.IsNullOrWhiteSpace(configuration)) if (string.IsNullOrWhiteSpace(configuration))
{ {
throw new ArgumentException("is empty", configuration); throw new ArgumentException("is empty", configuration);
} }
Clear(); Clear();
// break it down by commas // break it down by commas
var arr = configuration.Split(StringSplits.Comma); var arr = configuration.Split(StringSplits.Comma);
Dictionary<string, string> map = null; Dictionary<string, string> map = null;
foreach (var paddedOption in arr) foreach (var paddedOption in arr)
{ {
var option = paddedOption.Trim(); var option = paddedOption.Trim();
if (string.IsNullOrWhiteSpace(option)) continue; if (string.IsNullOrWhiteSpace(option)) continue;
// check for special tokens // check for special tokens
int idx = option.IndexOf('='); int idx = option.IndexOf('=');
if (idx > 0) if (idx > 0)
{ {
var key = option.Substring(0, idx).Trim(); var key = option.Substring(0, idx).Trim();
var value = option.Substring(idx + 1).Trim(); var value = option.Substring(idx + 1).Trim();
switch (OptionKeys.TryNormalize(key)) switch (OptionKeys.TryNormalize(key))
{ {
case OptionKeys.SyncTimeout: case OptionKeys.SyncTimeout:
SyncTimeout = OptionKeys.ParseInt32(key, value, minValue: 1); SyncTimeout = OptionKeys.ParseInt32(key, value, minValue: 1);
break; break;
case OptionKeys.AllowAdmin: case OptionKeys.AllowAdmin:
AllowAdmin = OptionKeys.ParseBoolean(key, value); AllowAdmin = OptionKeys.ParseBoolean(key, value);
break; break;
case OptionKeys.AbortOnConnectFail: case OptionKeys.AbortOnConnectFail:
AbortOnConnectFail = OptionKeys.ParseBoolean(key, value); AbortOnConnectFail = OptionKeys.ParseBoolean(key, value);
break; break;
case OptionKeys.ResolveDns: case OptionKeys.ResolveDns:
ResolveDns = OptionKeys.ParseBoolean(key, value); ResolveDns = OptionKeys.ParseBoolean(key, value);
break; break;
case OptionKeys.ServiceName: case OptionKeys.ServiceName:
ServiceName = value; ServiceName = value;
break; break;
case OptionKeys.ClientName: case OptionKeys.ClientName:
ClientName = value; ClientName = value;
break; break;
case OptionKeys.ChannelPrefix: case OptionKeys.ChannelPrefix:
ChannelPrefix = value; ChannelPrefix = value;
break; break;
case OptionKeys.ConfigChannel: case OptionKeys.ConfigChannel:
ConfigurationChannel = value; ConfigurationChannel = value;
break; break;
case OptionKeys.KeepAlive: case OptionKeys.KeepAlive:
KeepAlive = OptionKeys.ParseInt32(key, value); KeepAlive = OptionKeys.ParseInt32(key, value);
break; break;
case OptionKeys.ConnectTimeout: case OptionKeys.ConnectTimeout:
ConnectTimeout = OptionKeys.ParseInt32(key, value); ConnectTimeout = OptionKeys.ParseInt32(key, value);
break; break;
case OptionKeys.ConnectRetry: case OptionKeys.ConnectRetry:
ConnectRetry = OptionKeys.ParseInt32(key, value); ConnectRetry = OptionKeys.ParseInt32(key, value);
break; break;
case OptionKeys.ConfigCheckSeconds: case OptionKeys.ConfigCheckSeconds:
ConfigCheckSeconds = OptionKeys.ParseInt32(key, value); ConfigCheckSeconds = OptionKeys.ParseInt32(key, value);
break; break;
case OptionKeys.Version: case OptionKeys.Version:
DefaultVersion = OptionKeys.ParseVersion(key, value); DefaultVersion = OptionKeys.ParseVersion(key, value);
break; break;
case OptionKeys.Password: case OptionKeys.Password:
Password = value; Password = value;
break; break;
case OptionKeys.TieBreaker: case OptionKeys.TieBreaker:
TieBreaker = value; TieBreaker = value;
break; break;
case OptionKeys.Ssl: case OptionKeys.Ssl:
Ssl = OptionKeys.ParseBoolean(key, value); Ssl = OptionKeys.ParseBoolean(key, value);
break; break;
case OptionKeys.SslHost: case OptionKeys.SslHost:
SslHost = value; SslHost = value;
break; break;
case OptionKeys.HighPrioritySocketThreads: case OptionKeys.HighPrioritySocketThreads:
HighPrioritySocketThreads = OptionKeys.ParseBoolean(key, value); HighPrioritySocketThreads = OptionKeys.ParseBoolean(key, value);
break; break;
case OptionKeys.WriteBuffer: case OptionKeys.WriteBuffer:
WriteBuffer = OptionKeys.ParseInt32(key, value); WriteBuffer = OptionKeys.ParseInt32(key, value);
break; break;
case OptionKeys.Proxy: case OptionKeys.Proxy:
Proxy = OptionKeys.ParseProxy(key, value); Proxy = OptionKeys.ParseProxy(key, value);
break; break;
case OptionKeys.ResponseTimeout: case OptionKeys.ResponseTimeout:
ResponseTimeout = OptionKeys.ParseInt32(key, value, minValue: 1); ResponseTimeout = OptionKeys.ParseInt32(key, value, minValue: 1);
break; break;
case OptionKeys.DefaultDatabase: case OptionKeys.DefaultDatabase:
DefaultDatabase = OptionKeys.ParseInt32(key, value); DefaultDatabase = OptionKeys.ParseInt32(key, value);
break; break;
case OptionKeys.PreserveAsyncOrder: case OptionKeys.PreserveAsyncOrder:
PreserveAsyncOrder = OptionKeys.ParseBoolean(key, value); PreserveAsyncOrder = OptionKeys.ParseBoolean(key, value);
break; break;
#if !NETSTANDARD1_5 #if !NETSTANDARD1_5
case OptionKeys.SslProtocols: case OptionKeys.SslProtocols:
SslProtocols = OptionKeys.ParseSslProtocols(key, value); SslProtocols = OptionKeys.ParseSslProtocols(key, value);
break; break;
#endif #endif
default: default:
if (!string.IsNullOrEmpty(key) && key[0] == '$') if (!string.IsNullOrEmpty(key) && key[0] == '$')
{ {
var cmdName = option.Substring(1, idx - 1); var cmdName = option.Substring(1, idx - 1);
if (Enum.TryParse(cmdName, true, out RedisCommand cmd)) if (Enum.TryParse(cmdName, true, out RedisCommand cmd))
{ {
if (map == null) map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); if (map == null) map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
map[cmdName] = value; map[cmdName] = value;
} }
} }
else else
{ {
if (!ignoreUnknown) OptionKeys.Unknown(key); if (!ignoreUnknown) OptionKeys.Unknown(key);
} }
break; break;
} }
} }
else else
{ {
var ep = Format.TryParseEndPoint(option); var ep = Format.TryParseEndPoint(option);
if (ep != null && !EndPoints.Contains(ep)) EndPoints.Add(ep); if (ep != null && !EndPoints.Contains(ep)) EndPoints.Add(ep);
} }
} }
if (map != null && map.Count != 0) if (map != null && map.Count != 0)
{ {
CommandMap = CommandMap.Create(map); CommandMap = CommandMap.Create(map);
} }
} }
private bool GetDefaultAbortOnConnectFailSetting() private bool GetDefaultAbortOnConnectFailSetting()
{ {
// Microsoft Azure team wants abortConnect=false by default // Microsoft Azure team wants abortConnect=false by default
if (IsAzureEndpoint()) if (IsAzureEndpoint())
return false; return false;
return true; return true;
} }
private bool IsAzureEndpoint() private bool IsAzureEndpoint()
{ {
foreach (var ep in EndPoints) foreach (var ep in EndPoints)
{ {
if (ep is DnsEndPoint dnsEp) if (ep is DnsEndPoint dnsEp)
{ {
int firstDot = dnsEp.Host.IndexOf('.'); int firstDot = dnsEp.Host.IndexOf('.');
if (firstDot >= 0) if (firstDot >= 0)
{ {
switch (dnsEp.Host.Substring(firstDot).ToLowerInvariant()) switch (dnsEp.Host.Substring(firstDot).ToLowerInvariant())
{ {
case ".redis.cache.windows.net": case ".redis.cache.windows.net":
case ".redis.cache.chinacloudapi.cn": case ".redis.cache.chinacloudapi.cn":
case ".redis.cache.usgovcloudapi.net": case ".redis.cache.usgovcloudapi.net":
case ".redis.cache.cloudapi.de": case ".redis.cache.cloudapi.de":
return true; return true;
} }
} }
} }
} }
return false; return false;
} }
private string InferSslHostFromEndpoints() private string InferSslHostFromEndpoints()
{ {
var dnsEndpoints = EndPoints.Select(endpoint => endpoint as DnsEndPoint); var dnsEndpoints = EndPoints.Select(endpoint => endpoint as DnsEndPoint);
string dnsHost = dnsEndpoints.FirstOrDefault()?.Host; string dnsHost = dnsEndpoints.FirstOrDefault()?.Host;
if (dnsEndpoints.All(dnsEndpoint => (dnsEndpoint != null && dnsEndpoint.Host == dnsHost))) if (dnsEndpoints.All(dnsEndpoint => (dnsEndpoint != null && dnsEndpoint.Host == dnsHost)))
{ {
return dnsHost; return dnsHost;
} }
return null; return null;
} }
} }
} }
\ No newline at end of file
using System; using System;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
public partial class ConnectionMultiplexer public partial class ConnectionMultiplexer
{ {
private IProfiler profiler; private IProfiler profiler;
// internal for test purposes // internal for test purposes
internal ProfileContextTracker profiledCommands; internal ProfileContextTracker profiledCommands;
/// <summary> /// <summary>
/// Sets an IProfiler instance for this ConnectionMultiplexer. /// Sets an IProfiler instance for this ConnectionMultiplexer.
/// ///
/// An IProfiler instances is used to determine which context to associate an /// An IProfiler instances is used to determine which context to associate an
/// IProfiledCommand with. See BeginProfiling(object) and FinishProfiling(object) /// IProfiledCommand with. See BeginProfiling(object) and FinishProfiling(object)
/// for more details. /// for more details.
/// </summary> /// </summary>
/// <param name="profiler">The profiler to register.</param> /// <param name="profiler">The profiler to register.</param>
public void RegisterProfiler(IProfiler profiler) public void RegisterProfiler(IProfiler profiler)
{ {
if (this.profiler != null) throw new InvalidOperationException("IProfiler already registered for this ConnectionMultiplexer"); if (this.profiler != null) throw new InvalidOperationException("IProfiler already registered for this ConnectionMultiplexer");
this.profiler = profiler ?? throw new ArgumentNullException(nameof(profiler)); this.profiler = profiler ?? throw new ArgumentNullException(nameof(profiler));
profiledCommands = new ProfileContextTracker(); profiledCommands = new ProfileContextTracker();
} }
/// <summary> /// <summary>
/// Begins profiling for the given context. /// Begins profiling for the given context.
/// ///
/// If the same context object is returned by the registered IProfiler, the IProfiledCommands /// If the same context object is returned by the registered IProfiler, the IProfiledCommands
/// will be associated with each other. /// will be associated with each other.
/// ///
/// Call FinishProfiling with the same context to get the assocated commands. /// Call FinishProfiling with the same context to get the assocated commands.
/// ///
/// Note that forContext cannot be a WeakReference or a WeakReference&lt;T&gt; /// Note that forContext cannot be a WeakReference or a WeakReference&lt;T&gt;
/// </summary> /// </summary>
/// <param name="forContext">The context to begin profiling.</param> /// <param name="forContext">The context to begin profiling.</param>
public void BeginProfiling(object forContext) public void BeginProfiling(object forContext)
{ {
if (profiler == null) throw new InvalidOperationException("Cannot begin profiling if no IProfiler has been registered with RegisterProfiler"); if (profiler == null) throw new InvalidOperationException("Cannot begin profiling if no IProfiler has been registered with RegisterProfiler");
if (forContext == null) throw new ArgumentNullException(nameof(forContext)); if (forContext == null) throw new ArgumentNullException(nameof(forContext));
if (forContext is WeakReference) throw new ArgumentException("Context object cannot be a WeakReference", nameof(forContext)); if (forContext is WeakReference) throw new ArgumentException("Context object cannot be a WeakReference", nameof(forContext));
if (!profiledCommands.TryCreate(forContext)) if (!profiledCommands.TryCreate(forContext))
{ {
throw ExceptionFactory.BeganProfilingWithDuplicateContext(forContext); throw ExceptionFactory.BeganProfilingWithDuplicateContext(forContext);
} }
} }
/// <summary> /// <summary>
/// Stops profiling for the given context, returns all IProfiledCommands associated. /// Stops profiling for the given context, returns all IProfiledCommands associated.
/// ///
/// By default this may do a sweep for dead profiling contexts, you can disable this by passing "allowCleanupSweep: false". /// By default this may do a sweep for dead profiling contexts, you can disable this by passing "allowCleanupSweep: false".
/// </summary> /// </summary>
/// <param name="forContext">The context to begin profiling.</param> /// <param name="forContext">The context to begin profiling.</param>
/// <param name="allowCleanupSweep">Whether to allow cleanup of old profiling sessions.</param> /// <param name="allowCleanupSweep">Whether to allow cleanup of old profiling sessions.</param>
public ProfiledCommandEnumerable FinishProfiling(object forContext, bool allowCleanupSweep = true) public ProfiledCommandEnumerable FinishProfiling(object forContext, bool allowCleanupSweep = true)
{ {
if (profiler == null) throw new InvalidOperationException("Cannot begin profiling if no IProfiler has been registered with RegisterProfiler"); if (profiler == null) throw new InvalidOperationException("Cannot begin profiling if no IProfiler has been registered with RegisterProfiler");
if (forContext == null) throw new ArgumentNullException(nameof(forContext)); if (forContext == null) throw new ArgumentNullException(nameof(forContext));
if (!profiledCommands.TryRemove(forContext, out ProfiledCommandEnumerable ret)) if (!profiledCommands.TryRemove(forContext, out ProfiledCommandEnumerable ret))
{ {
throw ExceptionFactory.FinishedProfilingWithInvalidContext(forContext); throw ExceptionFactory.FinishedProfilingWithInvalidContext(forContext);
} }
// conditional, because it could hurt and that may sometimes be unacceptable // conditional, because it could hurt and that may sometimes be unacceptable
if (allowCleanupSweep) if (allowCleanupSweep)
{ {
profiledCommands.TryCleanup(); profiledCommands.TryCleanup();
} }
return ret; return ret;
} }
} }
} }
namespace StackExchange.Redis.KeyspaceIsolation namespace StackExchange.Redis.KeyspaceIsolation
{ {
internal sealed class BatchWrapper : WrapperBase<IBatch>, IBatch internal sealed class BatchWrapper : WrapperBase<IBatch>, IBatch
{ {
public BatchWrapper(IBatch inner, byte[] prefix) : base(inner, prefix) public BatchWrapper(IBatch inner, byte[] prefix) : base(inner, prefix)
{ {
} }
public void Execute() public void Execute()
{ {
Inner.Execute(); Inner.Execute();
} }
} }
} }
using System; using System;
namespace StackExchange.Redis.KeyspaceIsolation namespace StackExchange.Redis.KeyspaceIsolation
{ {
/// <summary> /// <summary>
/// Provides the <see cref="WithKeyPrefix"/> extension method to <see cref="IDatabase"/>. /// Provides the <see cref="WithKeyPrefix"/> extension method to <see cref="IDatabase"/>.
/// </summary> /// </summary>
public static class DatabaseExtensions public static class DatabaseExtensions
{ {
/// <summary> /// <summary>
/// Creates a new <see cref="IDatabase"/> instance that provides an isolated key space /// Creates a new <see cref="IDatabase"/> instance that provides an isolated key space
/// of the specified underyling database instance. /// of the specified underyling database instance.
/// </summary> /// </summary>
/// <param name="database"> /// <param name="database">
/// The underlying database instance that the returned instance shall use. /// The underlying database instance that the returned instance shall use.
/// </param> /// </param>
/// <param name="keyPrefix"> /// <param name="keyPrefix">
/// The prefix that defines a key space isolation for the returned database instance. /// The prefix that defines a key space isolation for the returned database instance.
/// </param> /// </param>
/// <returns> /// <returns>
/// A new <see cref="IDatabase"/> instance that invokes the specified underlying /// A new <see cref="IDatabase"/> instance that invokes the specified underlying
/// <paramref name="database"/> but prepends the specified <paramref name="keyPrefix"/> /// <paramref name="database"/> but prepends the specified <paramref name="keyPrefix"/>
/// to all key paramters and thus forms a logical key space isolation. /// to all key paramters and thus forms a logical key space isolation.
/// </returns> /// </returns>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// The following methods are not supported in a key space isolated database and /// The following methods are not supported in a key space isolated database and
/// will throw an <see cref="NotSupportedException"/> when invoked: /// will throw an <see cref="NotSupportedException"/> when invoked:
/// </para> /// </para>
/// <list type="bullet"> /// <list type="bullet">
/// <item><see cref="IDatabaseAsync.KeyRandomAsync(CommandFlags)"/></item> /// <item><see cref="IDatabaseAsync.KeyRandomAsync(CommandFlags)"/></item>
/// <item><see cref="IDatabase.KeyRandom(CommandFlags)"/></item> /// <item><see cref="IDatabase.KeyRandom(CommandFlags)"/></item>
/// </list> /// </list>
/// <para> /// <para>
/// Please notice that keys passed to a script are prefixed (as normal) but care must /// Please notice that keys passed to a script are prefixed (as normal) but care must
/// be taken when a script returns the name of a key as that will (currently) not be /// be taken when a script returns the name of a key as that will (currently) not be
/// "unprefixed". /// "unprefixed".
/// </para> /// </para>
/// </remarks> /// </remarks>
public static IDatabase WithKeyPrefix(this IDatabase database, RedisKey keyPrefix) public static IDatabase WithKeyPrefix(this IDatabase database, RedisKey keyPrefix)
{ {
if (database == null) if (database == null)
{ {
throw new ArgumentNullException(nameof(database)); throw new ArgumentNullException(nameof(database));
} }
if (keyPrefix.IsNull) if (keyPrefix.IsNull)
{ {
throw new ArgumentNullException(nameof(keyPrefix)); throw new ArgumentNullException(nameof(keyPrefix));
} }
if (keyPrefix.IsEmpty) if (keyPrefix.IsEmpty)
{ {
return database; // fine - you can keep using the original, then return database; // fine - you can keep using the original, then
} }
if (database is DatabaseWrapper wrapper) if (database is DatabaseWrapper wrapper)
{ {
// combine the key in advance to minimize indirection // combine the key in advance to minimize indirection
keyPrefix = wrapper.ToInner(keyPrefix); keyPrefix = wrapper.ToInner(keyPrefix);
database = wrapper.Inner; database = wrapper.Inner;
} }
return new DatabaseWrapper(database, keyPrefix.AsPrefix()); return new DatabaseWrapper(database, keyPrefix.AsPrefix());
} }
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
namespace StackExchange.Redis.KeyspaceIsolation namespace StackExchange.Redis.KeyspaceIsolation
{ {
internal sealed class DatabaseWrapper : WrapperBase<IDatabase>, IDatabase internal sealed class DatabaseWrapper : WrapperBase<IDatabase>, IDatabase
{ {
public DatabaseWrapper(IDatabase inner, byte[] prefix) : base(inner, prefix) public DatabaseWrapper(IDatabase inner, byte[] prefix) : base(inner, prefix)
{ {
} }
public IBatch CreateBatch(object asyncState = null) public IBatch CreateBatch(object asyncState = null)
{ {
return new BatchWrapper(Inner.CreateBatch(asyncState), Prefix); return new BatchWrapper(Inner.CreateBatch(asyncState), Prefix);
} }
public ITransaction CreateTransaction(object asyncState = null) public ITransaction CreateTransaction(object asyncState = null)
{ {
return new TransactionWrapper(Inner.CreateTransaction(asyncState), Prefix); return new TransactionWrapper(Inner.CreateTransaction(asyncState), Prefix);
} }
public int Database => Inner.Database; public int Database => Inner.Database;
public RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.None) public RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.DebugObject(ToInner(key), flags); return Inner.DebugObject(ToInner(key), flags);
} }
public bool GeoAdd(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None) public bool GeoAdd(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None)
{ {
return Inner.GeoAdd(ToInner(key), longitude, latitude, member, flags); return Inner.GeoAdd(ToInner(key), longitude, latitude, member, flags);
} }
public long GeoAdd(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None) public long GeoAdd(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None)
{ {
return Inner.GeoAdd(ToInner(key), values, flags); return Inner.GeoAdd(ToInner(key), values, flags);
} }
public bool GeoAdd(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None) public bool GeoAdd(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.GeoAdd(ToInner(key), value, flags); return Inner.GeoAdd(ToInner(key), value, flags);
} }
public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{ {
return Inner.GeoRemove(ToInner(key), member, flags); return Inner.GeoRemove(ToInner(key), member, flags);
} }
public double? GeoDistance(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters,CommandFlags flags = CommandFlags.None) public double? GeoDistance(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters,CommandFlags flags = CommandFlags.None)
{ {
return Inner.GeoDistance(ToInner(key), member1, member2, unit, flags); return Inner.GeoDistance(ToInner(key), member1, member2, unit, flags);
} }
public string[] GeoHash(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) public string[] GeoHash(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
{ {
return Inner.GeoHash(ToInner(key), members, flags); return Inner.GeoHash(ToInner(key), members, flags);
} }
public string GeoHash(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) public string GeoHash(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{ {
return Inner.GeoHash(ToInner(key), member, flags); return Inner.GeoHash(ToInner(key), member, flags);
} }
public GeoPosition?[] GeoPosition(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) public GeoPosition?[] GeoPosition(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
{ {
return Inner.GeoPosition(ToInner(key), members, flags); return Inner.GeoPosition(ToInner(key), members, flags);
} }
public GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) public GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{ {
return Inner.GeoPosition(ToInner(key), member, flags); return Inner.GeoPosition(ToInner(key), member, flags);
} }
public GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null,GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) public GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null,GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None)
{ {
return Inner.GeoRadius(ToInner(key), member, radius, unit, count, order, options, flags); return Inner.GeoRadius(ToInner(key), member, radius, unit, count, order, options, flags);
} }
public GeoRadiusResult[] GeoRadius(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) public GeoRadiusResult[] GeoRadius(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None)
{ {
return Inner.GeoRadius(ToInner(key), longitude, latitude, radius, unit, count, order, options, flags); return Inner.GeoRadius(ToInner(key), longitude, latitude, radius, unit, count, order, options, flags);
} }
public double HashDecrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) public double HashDecrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashDecrement(ToInner(key), hashField, value, flags); return Inner.HashDecrement(ToInner(key), hashField, value, flags);
} }
public long HashDecrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) public long HashDecrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashDecrement(ToInner(key), hashField, value, flags); return Inner.HashDecrement(ToInner(key), hashField, value, flags);
} }
public long HashDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) public long HashDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashDelete(ToInner(key), hashFields, flags); return Inner.HashDelete(ToInner(key), hashFields, flags);
} }
public bool HashDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) public bool HashDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashDelete(ToInner(key), hashField, flags); return Inner.HashDelete(ToInner(key), hashField, flags);
} }
public bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) public bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashExists(ToInner(key), hashField, flags); return Inner.HashExists(ToInner(key), hashField, flags);
} }
public HashEntry[] HashGetAll(RedisKey key, CommandFlags flags = CommandFlags.None) public HashEntry[] HashGetAll(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashGetAll(ToInner(key), flags); return Inner.HashGetAll(ToInner(key), flags);
} }
public RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) public RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashGet(ToInner(key), hashFields, flags); return Inner.HashGet(ToInner(key), hashFields, flags);
} }
public RedisValue HashGet(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) public RedisValue HashGet(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashGet(ToInner(key), hashField, flags); return Inner.HashGet(ToInner(key), hashField, flags);
} }
public double HashIncrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) public double HashIncrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashIncrement(ToInner(key), hashField, value, flags); return Inner.HashIncrement(ToInner(key), hashField, value, flags);
} }
public long HashIncrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) public long HashIncrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashIncrement(ToInner(key), hashField, value, flags); return Inner.HashIncrement(ToInner(key), hashField, value, flags);
} }
public RedisValue[] HashKeys(RedisKey key, CommandFlags flags = CommandFlags.None) public RedisValue[] HashKeys(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashKeys(ToInner(key), flags); return Inner.HashKeys(ToInner(key), flags);
} }
public long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None) public long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashLength(ToInner(key), flags); return Inner.HashLength(ToInner(key), flags);
} }
public bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) public bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashSet(ToInner(key), hashField, value, when, flags); return Inner.HashSet(ToInner(key), hashField, value, when, flags);
} }
public void HashSet(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None) public void HashSet(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None)
{ {
Inner.HashSet(ToInner(key), hashFields, flags); Inner.HashSet(ToInner(key), hashFields, flags);
} }
public RedisValue[] HashValues(RedisKey key, CommandFlags flags = CommandFlags.None) public RedisValue[] HashValues(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashValues(ToInner(key), flags); return Inner.HashValues(ToInner(key), flags);
} }
public bool HyperLogLogAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) public bool HyperLogLogAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HyperLogLogAdd(ToInner(key), values, flags); return Inner.HyperLogLogAdd(ToInner(key), values, flags);
} }
public bool HyperLogLogAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public bool HyperLogLogAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HyperLogLogAdd(ToInner(key), value, flags); return Inner.HyperLogLogAdd(ToInner(key), value, flags);
} }
public long HyperLogLogLength(RedisKey key, CommandFlags flags = CommandFlags.None) public long HyperLogLogLength(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HyperLogLogLength(ToInner(key), flags); return Inner.HyperLogLogLength(ToInner(key), flags);
} }
public long HyperLogLogLength(RedisKey[] keys, CommandFlags flags = CommandFlags.None) public long HyperLogLogLength(RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HyperLogLogLength(ToInner(keys), flags); return Inner.HyperLogLogLength(ToInner(keys), flags);
} }
public void HyperLogLogMerge(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None) public void HyperLogLogMerge(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None)
{ {
Inner.HyperLogLogMerge(ToInner(destination), ToInner(sourceKeys), flags); Inner.HyperLogLogMerge(ToInner(destination), ToInner(sourceKeys), flags);
} }
public void HyperLogLogMerge(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) public void HyperLogLogMerge(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None)
{ {
Inner.HyperLogLogMerge(ToInner(destination), ToInner(first), ToInner(second), flags); Inner.HyperLogLogMerge(ToInner(destination), ToInner(first), ToInner(second), flags);
} }
public EndPoint IdentifyEndpoint(RedisKey key = default(RedisKey), CommandFlags flags = CommandFlags.None) public EndPoint IdentifyEndpoint(RedisKey key = default(RedisKey), CommandFlags flags = CommandFlags.None)
{ {
return Inner.IdentifyEndpoint(ToInner(key), flags); return Inner.IdentifyEndpoint(ToInner(key), flags);
} }
public long KeyDelete(RedisKey[] keys, CommandFlags flags = CommandFlags.None) public long KeyDelete(RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyDelete(ToInner(keys), flags); return Inner.KeyDelete(ToInner(keys), flags);
} }
public bool KeyDelete(RedisKey key, CommandFlags flags = CommandFlags.None) public bool KeyDelete(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyDelete(ToInner(key), flags); return Inner.KeyDelete(ToInner(key), flags);
} }
public byte[] KeyDump(RedisKey key, CommandFlags flags = CommandFlags.None) public byte[] KeyDump(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyDump(ToInner(key), flags); return Inner.KeyDump(ToInner(key), flags);
} }
public bool KeyExists(RedisKey key, CommandFlags flags = CommandFlags.None) public bool KeyExists(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyExists(ToInner(key), flags); return Inner.KeyExists(ToInner(key), flags);
} }
public bool KeyExpire(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None) public bool KeyExpire(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyExpire(ToInner(key), expiry, flags); return Inner.KeyExpire(ToInner(key), expiry, flags);
} }
public bool KeyExpire(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) public bool KeyExpire(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyExpire(ToInner(key), expiry, flags); return Inner.KeyExpire(ToInner(key), expiry, flags);
} }
public void KeyMigrate(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None) public void KeyMigrate(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None)
{ {
Inner.KeyMigrate(ToInner(key), toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags); Inner.KeyMigrate(ToInner(key), toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags);
} }
public bool KeyMove(RedisKey key, int database, CommandFlags flags = CommandFlags.None) public bool KeyMove(RedisKey key, int database, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyMove(ToInner(key), database, flags); return Inner.KeyMove(ToInner(key), database, flags);
} }
public bool KeyPersist(RedisKey key, CommandFlags flags = CommandFlags.None) public bool KeyPersist(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyPersist(ToInner(key), flags); return Inner.KeyPersist(ToInner(key), flags);
} }
public RedisKey KeyRandom(CommandFlags flags = CommandFlags.None) public RedisKey KeyRandom(CommandFlags flags = CommandFlags.None)
{ {
throw new NotSupportedException("RANDOMKEY is not supported when a key-prefix is specified"); throw new NotSupportedException("RANDOMKEY is not supported when a key-prefix is specified");
} }
public bool KeyRename(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) public bool KeyRename(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyRename(ToInner(key), ToInner(newKey), when, flags); return Inner.KeyRename(ToInner(key), ToInner(newKey), when, flags);
} }
public void KeyRestore(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None) public void KeyRestore(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None)
{ {
Inner.KeyRestore(ToInner(key), value, expiry, flags); Inner.KeyRestore(ToInner(key), value, expiry, flags);
} }
public TimeSpan? KeyTimeToLive(RedisKey key, CommandFlags flags = CommandFlags.None) public TimeSpan? KeyTimeToLive(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyTimeToLive(ToInner(key), flags); return Inner.KeyTimeToLive(ToInner(key), flags);
} }
public RedisType KeyType(RedisKey key, CommandFlags flags = CommandFlags.None) public RedisType KeyType(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyType(ToInner(key), flags); return Inner.KeyType(ToInner(key), flags);
} }
public RedisValue ListGetByIndex(RedisKey key, long index, CommandFlags flags = CommandFlags.None) public RedisValue ListGetByIndex(RedisKey key, long index, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListGetByIndex(ToInner(key), index, flags); return Inner.ListGetByIndex(ToInner(key), index, flags);
} }
public long ListInsertAfter(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) public long ListInsertAfter(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListInsertAfter(ToInner(key), pivot, value, flags); return Inner.ListInsertAfter(ToInner(key), pivot, value, flags);
} }
public long ListInsertBefore(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) public long ListInsertBefore(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListInsertBefore(ToInner(key), pivot, value, flags); return Inner.ListInsertBefore(ToInner(key), pivot, value, flags);
} }
public RedisValue ListLeftPop(RedisKey key, CommandFlags flags = CommandFlags.None) public RedisValue ListLeftPop(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListLeftPop(ToInner(key), flags); return Inner.ListLeftPop(ToInner(key), flags);
} }
public long ListLeftPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) public long ListLeftPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListLeftPush(ToInner(key), values, flags); return Inner.ListLeftPush(ToInner(key), values, flags);
} }
public long ListLeftPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) public long ListLeftPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListLeftPush(ToInner(key), value, when, flags); return Inner.ListLeftPush(ToInner(key), value, when, flags);
} }
public long ListLength(RedisKey key, CommandFlags flags = CommandFlags.None) public long ListLength(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListLength(ToInner(key), flags); return Inner.ListLength(ToInner(key), flags);
} }
public RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None) public RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListRange(ToInner(key), start, stop, flags); return Inner.ListRange(ToInner(key), start, stop, flags);
} }
public long ListRemove(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None) public long ListRemove(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListRemove(ToInner(key), value, count, flags); return Inner.ListRemove(ToInner(key), value, count, flags);
} }
public RedisValue ListRightPop(RedisKey key, CommandFlags flags = CommandFlags.None) public RedisValue ListRightPop(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListRightPop(ToInner(key), flags); return Inner.ListRightPop(ToInner(key), flags);
} }
public RedisValue ListRightPopLeftPush(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None) public RedisValue ListRightPopLeftPush(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListRightPopLeftPush(ToInner(source), ToInner(destination), flags); return Inner.ListRightPopLeftPush(ToInner(source), ToInner(destination), flags);
} }
public long ListRightPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) public long ListRightPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListRightPush(ToInner(key), values, flags); return Inner.ListRightPush(ToInner(key), values, flags);
} }
public long ListRightPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) public long ListRightPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListRightPush(ToInner(key), value, when, flags); return Inner.ListRightPush(ToInner(key), value, when, flags);
} }
public void ListSetByIndex(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None) public void ListSetByIndex(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
Inner.ListSetByIndex(ToInner(key), index, value, flags); Inner.ListSetByIndex(ToInner(key), index, value, flags);
} }
public void ListTrim(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) public void ListTrim(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None)
{ {
Inner.ListTrim(ToInner(key), start, stop, flags); Inner.ListTrim(ToInner(key), start, stop, flags);
} }
public bool LockExtend(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) public bool LockExtend(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None)
{ {
return Inner.LockExtend(ToInner(key), value, expiry, flags); return Inner.LockExtend(ToInner(key), value, expiry, flags);
} }
public RedisValue LockQuery(RedisKey key, CommandFlags flags = CommandFlags.None) public RedisValue LockQuery(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.LockQuery(ToInner(key), flags); return Inner.LockQuery(ToInner(key), flags);
} }
public bool LockRelease(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public bool LockRelease(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.LockRelease(ToInner(key), value, flags); return Inner.LockRelease(ToInner(key), value, flags);
} }
public bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) public bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None)
{ {
return Inner.LockTake(ToInner(key), value, expiry, flags); return Inner.LockTake(ToInner(key), value, expiry, flags);
} }
public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None)
{ {
return Inner.Publish(ToInner(channel), message, flags); return Inner.Publish(ToInner(channel), message, flags);
} }
public RedisResult Execute(string command, params object[] args) public RedisResult Execute(string command, params object[] args)
=> Inner.Execute(command, ToInner(args), CommandFlags.None); => Inner.Execute(command, ToInner(args), CommandFlags.None);
public RedisResult Execute(string command, ICollection<object> args, CommandFlags flags = CommandFlags.None) public RedisResult Execute(string command, ICollection<object> args, CommandFlags flags = CommandFlags.None)
=> Inner.Execute(command, ToInner(args), flags); => Inner.Execute(command, ToInner(args), flags);
public RedisResult ScriptEvaluate(byte[] hash, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) public RedisResult ScriptEvaluate(byte[] hash, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None)
{ {
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
return Inner.ScriptEvaluate(hash, ToInner(keys), values, flags); return Inner.ScriptEvaluate(hash, ToInner(keys), values, flags);
} }
public RedisResult ScriptEvaluate(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) public RedisResult ScriptEvaluate(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None)
{ {
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
return Inner.ScriptEvaluate(script, ToInner(keys), values, flags); return Inner.ScriptEvaluate(script, ToInner(keys), values, flags);
} }
public RedisResult ScriptEvaluate(LuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None) public RedisResult ScriptEvaluate(LuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None)
{ {
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
return script.Evaluate(Inner, parameters, Prefix, flags); return script.Evaluate(Inner, parameters, Prefix, flags);
} }
public RedisResult ScriptEvaluate(LoadedLuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None) public RedisResult ScriptEvaluate(LoadedLuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None)
{ {
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
return script.Evaluate(Inner, parameters, Prefix, flags); return script.Evaluate(Inner, parameters, Prefix, flags);
} }
public long SetAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) public long SetAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetAdd(ToInner(key), values, flags); return Inner.SetAdd(ToInner(key), values, flags);
} }
public bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetAdd(ToInner(key), value, flags); return Inner.SetAdd(ToInner(key), value, flags);
} }
public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetCombineAndStore(operation, ToInner(destination), ToInner(keys), flags); return Inner.SetCombineAndStore(operation, ToInner(destination), ToInner(keys), flags);
} }
public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetCombineAndStore(operation, ToInner(destination), ToInner(first), ToInner(second), flags); return Inner.SetCombineAndStore(operation, ToInner(destination), ToInner(first), ToInner(second), flags);
} }
public RedisValue[] SetCombine(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None) public RedisValue[] SetCombine(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetCombine(operation, ToInner(keys), flags); return Inner.SetCombine(operation, ToInner(keys), flags);
} }
public RedisValue[] SetCombine(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) public RedisValue[] SetCombine(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetCombine(operation, ToInner(first), ToInner(second), flags); return Inner.SetCombine(operation, ToInner(first), ToInner(second), flags);
} }
public bool SetContains(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public bool SetContains(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetContains(ToInner(key), value, flags); return Inner.SetContains(ToInner(key), value, flags);
} }
public long SetLength(RedisKey key, CommandFlags flags = CommandFlags.None) public long SetLength(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetLength(ToInner(key), flags); return Inner.SetLength(ToInner(key), flags);
} }
public RedisValue[] SetMembers(RedisKey key, CommandFlags flags = CommandFlags.None) public RedisValue[] SetMembers(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetMembers(ToInner(key), flags); return Inner.SetMembers(ToInner(key), flags);
} }
public bool SetMove(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None) public bool SetMove(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetMove(ToInner(source), ToInner(destination), value, flags); return Inner.SetMove(ToInner(source), ToInner(destination), value, flags);
} }
public RedisValue SetPop(RedisKey key, CommandFlags flags = CommandFlags.None) public RedisValue SetPop(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetPop(ToInner(key), flags); return Inner.SetPop(ToInner(key), flags);
} }
public RedisValue SetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) public RedisValue SetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetRandomMember(ToInner(key), flags); return Inner.SetRandomMember(ToInner(key), flags);
} }
public RedisValue[] SetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) public RedisValue[] SetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetRandomMembers(ToInner(key), count, flags); return Inner.SetRandomMembers(ToInner(key), count, flags);
} }
public long SetRemove(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) public long SetRemove(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetRemove(ToInner(key), values, flags); return Inner.SetRemove(ToInner(key), values, flags);
} }
public bool SetRemove(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public bool SetRemove(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetRemove(ToInner(key), value, flags); return Inner.SetRemove(ToInner(key), value, flags);
} }
public long SortAndStore(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None) public long SortAndStore(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortAndStore(ToInner(destination), ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags); return Inner.SortAndStore(ToInner(destination), ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags);
} }
public RedisValue[] Sort(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None) public RedisValue[] Sort(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None)
{ {
return Inner.Sort(ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags); return Inner.Sort(ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags);
} }
public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, CommandFlags flags) public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, CommandFlags flags)
{ {
return Inner.SortedSetAdd(ToInner(key), values, flags); return Inner.SortedSetAdd(ToInner(key), values, flags);
} }
public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetAdd(ToInner(key), values, when, flags); return Inner.SortedSetAdd(ToInner(key), values, when, flags);
} }
public bool SortedSetAdd(RedisKey key, RedisValue member, double score, CommandFlags flags) public bool SortedSetAdd(RedisKey key, RedisValue member, double score, CommandFlags flags)
{ {
return Inner.SortedSetAdd(ToInner(key), member, score, flags); return Inner.SortedSetAdd(ToInner(key), member, score, flags);
} }
public bool SortedSetAdd(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None) public bool SortedSetAdd(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetAdd(ToInner(key), member, score, when, flags); return Inner.SortedSetAdd(ToInner(key), member, score, when, flags);
} }
public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, double[] weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, double[] weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetCombineAndStore(operation, ToInner(destination), ToInner(keys), weights, aggregate, flags); return Inner.SortedSetCombineAndStore(operation, ToInner(destination), ToInner(keys), weights, aggregate, flags);
} }
public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetCombineAndStore(operation, ToInner(destination), ToInner(first), ToInner(second), aggregate, flags); return Inner.SortedSetCombineAndStore(operation, ToInner(destination), ToInner(first), ToInner(second), aggregate, flags);
} }
public double SortedSetDecrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) public double SortedSetDecrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetDecrement(ToInner(key), member, value, flags); return Inner.SortedSetDecrement(ToInner(key), member, value, flags);
} }
public double SortedSetIncrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) public double SortedSetIncrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetIncrement(ToInner(key), member, value, flags); return Inner.SortedSetIncrement(ToInner(key), member, value, flags);
} }
public long SortedSetLength(RedisKey key, double min = -1.0 / 0.0, double max = 1.0 / 0.0, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) public long SortedSetLength(RedisKey key, double min = -1.0 / 0.0, double max = 1.0 / 0.0, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetLength(ToInner(key), min, max, exclude, flags); return Inner.SortedSetLength(ToInner(key), min, max, exclude, flags);
} }
public long SortedSetLengthByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) public long SortedSetLengthByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetLengthByValue(ToInner(key), min, max, exclude, flags); return Inner.SortedSetLengthByValue(ToInner(key), min, max, exclude, flags);
} }
public RedisValue[] SortedSetRangeByRank(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) public RedisValue[] SortedSetRangeByRank(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRangeByRank(ToInner(key), start, stop, order, flags); return Inner.SortedSetRangeByRank(ToInner(key), start, stop, order, flags);
} }
public SortedSetEntry[] SortedSetRangeByRankWithScores(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) public SortedSetEntry[] SortedSetRangeByRankWithScores(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRangeByRankWithScores(ToInner(key), start, stop, order, flags); return Inner.SortedSetRangeByRankWithScores(ToInner(key), start, stop, order, flags);
} }
public RedisValue[] SortedSetRangeByScore(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) public RedisValue[] SortedSetRangeByScore(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRangeByScore(ToInner(key), start, stop, exclude, order, skip, take, flags); return Inner.SortedSetRangeByScore(ToInner(key), start, stop, exclude, order, skip, take, flags);
} }
public SortedSetEntry[] SortedSetRangeByScoreWithScores(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) public SortedSetEntry[] SortedSetRangeByScoreWithScores(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRangeByScoreWithScores(ToInner(key), start, stop, exclude, order, skip, take, flags); return Inner.SortedSetRangeByScoreWithScores(ToInner(key), start, stop, exclude, order, skip, take, flags);
} }
public RedisValue[] SortedSetRangeByValue(RedisKey key, RedisValue min = default(RedisValue), RedisValue max = default(RedisValue), Exclude exclude = Exclude.None, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) public RedisValue[] SortedSetRangeByValue(RedisKey key, RedisValue min = default(RedisValue), RedisValue max = default(RedisValue), Exclude exclude = Exclude.None, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRangeByValue(ToInner(key), min, max, exclude, skip, take, flags); return Inner.SortedSetRangeByValue(ToInner(key), min, max, exclude, skip, take, flags);
} }
public long? SortedSetRank(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) public long? SortedSetRank(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRank(ToInner(key), member, order, flags); return Inner.SortedSetRank(ToInner(key), member, order, flags);
} }
public long SortedSetRemove(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) public long SortedSetRemove(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRemove(ToInner(key), members, flags); return Inner.SortedSetRemove(ToInner(key), members, flags);
} }
public bool SortedSetRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) public bool SortedSetRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRemove(ToInner(key), member, flags); return Inner.SortedSetRemove(ToInner(key), member, flags);
} }
public long SortedSetRemoveRangeByRank(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) public long SortedSetRemoveRangeByRank(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRemoveRangeByRank(ToInner(key), start, stop, flags); return Inner.SortedSetRemoveRangeByRank(ToInner(key), start, stop, flags);
} }
public long SortedSetRemoveRangeByScore(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) public long SortedSetRemoveRangeByScore(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRemoveRangeByScore(ToInner(key), start, stop, exclude, flags); return Inner.SortedSetRemoveRangeByScore(ToInner(key), start, stop, exclude, flags);
} }
public long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) public long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRemoveRangeByValue(ToInner(key), min, max, exclude, flags); return Inner.SortedSetRemoveRangeByValue(ToInner(key), min, max, exclude, flags);
} }
public double? SortedSetScore(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) public double? SortedSetScore(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetScore(ToInner(key), member, flags); return Inner.SortedSetScore(ToInner(key), member, flags);
} }
public long StringAppend(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public long StringAppend(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringAppend(ToInner(key), value, flags); return Inner.StringAppend(ToInner(key), value, flags);
} }
public long StringBitCount(RedisKey key, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None) public long StringBitCount(RedisKey key, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringBitCount(ToInner(key), start, end, flags); return Inner.StringBitCount(ToInner(key), start, end, flags);
} }
public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringBitOperation(operation, ToInner(destination), ToInner(keys), flags); return Inner.StringBitOperation(operation, ToInner(destination), ToInner(keys), flags);
} }
public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second = default(RedisKey), CommandFlags flags = CommandFlags.None) public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second = default(RedisKey), CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringBitOperation(operation, ToInner(destination), ToInner(first), ToInnerOrDefault(second), flags); return Inner.StringBitOperation(operation, ToInner(destination), ToInner(first), ToInnerOrDefault(second), flags);
} }
public long StringBitPosition(RedisKey key, bool bit, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None) public long StringBitPosition(RedisKey key, bool bit, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringBitPosition(ToInner(key), bit, start, end, flags); return Inner.StringBitPosition(ToInner(key), bit, start, end, flags);
} }
public double StringDecrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None) public double StringDecrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringDecrement(ToInner(key), value, flags); return Inner.StringDecrement(ToInner(key), value, flags);
} }
public long StringDecrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) public long StringDecrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringDecrement(ToInner(key), value, flags); return Inner.StringDecrement(ToInner(key), value, flags);
} }
public RedisValue[] StringGet(RedisKey[] keys, CommandFlags flags = CommandFlags.None) public RedisValue[] StringGet(RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringGet(ToInner(keys), flags); return Inner.StringGet(ToInner(keys), flags);
} }
public RedisValue StringGet(RedisKey key, CommandFlags flags = CommandFlags.None) public RedisValue StringGet(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringGet(ToInner(key), flags); return Inner.StringGet(ToInner(key), flags);
} }
public bool StringGetBit(RedisKey key, long offset, CommandFlags flags = CommandFlags.None) public bool StringGetBit(RedisKey key, long offset, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringGetBit(ToInner(key), offset, flags); return Inner.StringGetBit(ToInner(key), offset, flags);
} }
public RedisValue StringGetRange(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None) public RedisValue StringGetRange(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringGetRange(ToInner(key), start, end, flags); return Inner.StringGetRange(ToInner(key), start, end, flags);
} }
public RedisValue StringGetSet(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public RedisValue StringGetSet(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringGetSet(ToInner(key), value, flags); return Inner.StringGetSet(ToInner(key), value, flags);
} }
public RedisValueWithExpiry StringGetWithExpiry(RedisKey key, CommandFlags flags = CommandFlags.None) public RedisValueWithExpiry StringGetWithExpiry(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringGetWithExpiry(ToInner(key), flags); return Inner.StringGetWithExpiry(ToInner(key), flags);
} }
public double StringIncrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None) public double StringIncrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringIncrement(ToInner(key), value, flags); return Inner.StringIncrement(ToInner(key), value, flags);
} }
public long StringIncrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) public long StringIncrement(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringIncrement(ToInner(key), value, flags); return Inner.StringIncrement(ToInner(key), value, flags);
} }
public long StringLength(RedisKey key, CommandFlags flags = CommandFlags.None) public long StringLength(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringLength(ToInner(key), flags); return Inner.StringLength(ToInner(key), flags);
} }
public bool StringSet(KeyValuePair<RedisKey, RedisValue>[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) public bool StringSet(KeyValuePair<RedisKey, RedisValue>[] values, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringSet(ToInner(values), when, flags); return Inner.StringSet(ToInner(values), when, flags);
} }
public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None) public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringSet(ToInner(key), value, expiry, when, flags); return Inner.StringSet(ToInner(key), value, expiry, when, flags);
} }
public bool StringSetBit(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None) public bool StringSetBit(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringSetBit(ToInner(key), offset, bit, flags); return Inner.StringSetBit(ToInner(key), offset, bit, flags);
} }
public RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) public RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringSetRange(ToInner(key), offset, value, flags); return Inner.StringSetRange(ToInner(key), offset, value, flags);
} }
public TimeSpan Ping(CommandFlags flags = CommandFlags.None) public TimeSpan Ping(CommandFlags flags = CommandFlags.None)
{ {
return Inner.Ping(flags); return Inner.Ping(flags);
} }
IEnumerable<HashEntry> IDatabase.HashScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) IEnumerable<HashEntry> IDatabase.HashScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags)
{ {
return HashScan(key, pattern, pageSize, RedisBase.CursorUtils.Origin, 0, flags); return HashScan(key, pattern, pageSize, RedisBase.CursorUtils.Origin, 0, flags);
} }
public IEnumerable<HashEntry> HashScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorUtils.DefaultPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) public IEnumerable<HashEntry> HashScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorUtils.DefaultPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); return Inner.HashScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);
} }
IEnumerable<RedisValue> IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) IEnumerable<RedisValue> IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags)
{ {
return SetScan(key, pattern, pageSize, RedisBase.CursorUtils.Origin, 0, flags); return SetScan(key, pattern, pageSize, RedisBase.CursorUtils.Origin, 0, flags);
} }
public IEnumerable<RedisValue> SetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorUtils.DefaultPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) public IEnumerable<RedisValue> SetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorUtils.DefaultPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); return Inner.SetScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);
} }
IEnumerable<SortedSetEntry> IDatabase.SortedSetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) IEnumerable<SortedSetEntry> IDatabase.SortedSetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags)
{ {
return SortedSetScan(key, pattern, pageSize, RedisBase.CursorUtils.Origin, 0, flags); return SortedSetScan(key, pattern, pageSize, RedisBase.CursorUtils.Origin, 0, flags);
} }
public IEnumerable<SortedSetEntry> SortedSetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorUtils.DefaultPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None) public IEnumerable<SortedSetEntry> SortedSetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorUtils.DefaultPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); return Inner.SortedSetScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);
} }
#if DEBUG #if DEBUG
public string ClientGetName(CommandFlags flags = CommandFlags.None) public string ClientGetName(CommandFlags flags = CommandFlags.None)
{ {
return Inner.ClientGetName(flags); return Inner.ClientGetName(flags);
} }
public void Quit(CommandFlags flags = CommandFlags.None) public void Quit(CommandFlags flags = CommandFlags.None)
{ {
Inner.Quit(flags); Inner.Quit(flags);
} }
#endif #endif
} }
} }
using System.Threading.Tasks; using System.Threading.Tasks;
namespace StackExchange.Redis.KeyspaceIsolation namespace StackExchange.Redis.KeyspaceIsolation
{ {
internal sealed class TransactionWrapper : WrapperBase<ITransaction>, ITransaction internal sealed class TransactionWrapper : WrapperBase<ITransaction>, ITransaction
{ {
public TransactionWrapper(ITransaction inner, byte[] prefix) : base(inner, prefix) public TransactionWrapper(ITransaction inner, byte[] prefix) : base(inner, prefix)
{ {
} }
public ConditionResult AddCondition(Condition condition) public ConditionResult AddCondition(Condition condition)
{ {
return Inner.AddCondition(condition?.MapKeys(GetMapFunction())); return Inner.AddCondition(condition?.MapKeys(GetMapFunction()));
} }
public bool Execute(CommandFlags flags = CommandFlags.None) public bool Execute(CommandFlags flags = CommandFlags.None)
{ {
return Inner.Execute(flags); return Inner.Execute(flags);
} }
public Task<bool> ExecuteAsync(CommandFlags flags = CommandFlags.None) public Task<bool> ExecuteAsync(CommandFlags flags = CommandFlags.None)
{ {
return Inner.ExecuteAsync(flags); return Inner.ExecuteAsync(flags);
} }
public void Execute() public void Execute()
{ {
Inner.Execute(); Inner.Execute();
} }
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace StackExchange.Redis.KeyspaceIsolation namespace StackExchange.Redis.KeyspaceIsolation
{ {
internal class WrapperBase<TInner> : IDatabaseAsync where TInner : IDatabaseAsync internal class WrapperBase<TInner> : IDatabaseAsync where TInner : IDatabaseAsync
{ {
internal WrapperBase(TInner inner, byte[] keyPrefix) internal WrapperBase(TInner inner, byte[] keyPrefix)
{ {
Inner = inner; Inner = inner;
Prefix = keyPrefix; Prefix = keyPrefix;
} }
public ConnectionMultiplexer Multiplexer => Inner.Multiplexer; public ConnectionMultiplexer Multiplexer => Inner.Multiplexer;
internal TInner Inner { get; } internal TInner Inner { get; }
internal byte[] Prefix { get; } internal byte[] Prefix { get; }
public Task<RedisValue> DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<RedisValue> DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.DebugObjectAsync(ToInner(key), flags); return Inner.DebugObjectAsync(ToInner(key), flags);
} }
public Task<bool> GeoAddAsync(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None) public Task<bool> GeoAddAsync(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None)
=> Inner.GeoAddAsync(ToInner(key), longitude, latitude, member, flags); => Inner.GeoAddAsync(ToInner(key), longitude, latitude, member, flags);
public Task<bool> GeoAddAsync(RedisKey key, StackExchange.Redis.GeoEntry value, CommandFlags flags = CommandFlags.None) public Task<bool> GeoAddAsync(RedisKey key, StackExchange.Redis.GeoEntry value, CommandFlags flags = CommandFlags.None)
=> Inner.GeoAddAsync(ToInner(key), value, flags); => Inner.GeoAddAsync(ToInner(key), value, flags);
public Task<long> GeoAddAsync(RedisKey key, StackExchange.Redis.GeoEntry[] values, CommandFlags flags = CommandFlags.None) public Task<long> GeoAddAsync(RedisKey key, StackExchange.Redis.GeoEntry[] values, CommandFlags flags = CommandFlags.None)
=> Inner.GeoAddAsync(ToInner(key), values, flags); => Inner.GeoAddAsync(ToInner(key), values, flags);
public Task<bool> GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) public Task<bool> GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
=> Inner.GeoRemoveAsync(ToInner(key), member, flags); => Inner.GeoRemoveAsync(ToInner(key), member, flags);
public Task<double?> GeoDistanceAsync(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None) public Task<double?> GeoDistanceAsync(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None)
=> Inner.GeoDistanceAsync(ToInner(key), member1, member2, unit, flags); => Inner.GeoDistanceAsync(ToInner(key), member1, member2, unit, flags);
public Task<string[]> GeoHashAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) public Task<string[]> GeoHashAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
=> Inner.GeoHashAsync(ToInner(key), members, flags); => Inner.GeoHashAsync(ToInner(key), members, flags);
public Task<string> GeoHashAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) public Task<string> GeoHashAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
=> Inner.GeoHashAsync(ToInner(key), member, flags); => Inner.GeoHashAsync(ToInner(key), member, flags);
public Task<GeoPosition?[]> GeoPositionAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) public Task<GeoPosition?[]> GeoPositionAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
=> Inner.GeoPositionAsync(ToInner(key), members, flags); => Inner.GeoPositionAsync(ToInner(key), members, flags);
public Task<GeoPosition?> GeoPositionAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) public Task<GeoPosition?> GeoPositionAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
=> Inner.GeoPositionAsync(ToInner(key), member, flags); => Inner.GeoPositionAsync(ToInner(key), member, flags);
public Task<GeoRadiusResult[]> GeoRadiusAsync(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) public Task<GeoRadiusResult[]> GeoRadiusAsync(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None)
=> Inner.GeoRadiusAsync(ToInner(key), member, radius, unit, count, order, options, flags); => Inner.GeoRadiusAsync(ToInner(key), member, radius, unit, count, order, options, flags);
public Task<GeoRadiusResult[]> GeoRadiusAsync(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) public Task<GeoRadiusResult[]> GeoRadiusAsync(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None)
=> Inner.GeoRadiusAsync(ToInner(key), longitude, latitude, radius, unit, count, order, options, flags); => Inner.GeoRadiusAsync(ToInner(key), longitude, latitude, radius, unit, count, order, options, flags);
public Task<double> HashDecrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) public Task<double> HashDecrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashDecrementAsync(ToInner(key), hashField, value, flags); return Inner.HashDecrementAsync(ToInner(key), hashField, value, flags);
} }
public Task<long> HashDecrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) public Task<long> HashDecrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashDecrementAsync(ToInner(key), hashField, value, flags); return Inner.HashDecrementAsync(ToInner(key), hashField, value, flags);
} }
public Task<long> HashDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) public Task<long> HashDeleteAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashDeleteAsync(ToInner(key), hashFields, flags); return Inner.HashDeleteAsync(ToInner(key), hashFields, flags);
} }
public Task<bool> HashDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) public Task<bool> HashDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashDeleteAsync(ToInner(key), hashField, flags); return Inner.HashDeleteAsync(ToInner(key), hashField, flags);
} }
public Task<bool> HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) public Task<bool> HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashExistsAsync(ToInner(key), hashField, flags); return Inner.HashExistsAsync(ToInner(key), hashField, flags);
} }
public Task<HashEntry[]> HashGetAllAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<HashEntry[]> HashGetAllAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashGetAllAsync(ToInner(key), flags); return Inner.HashGetAllAsync(ToInner(key), flags);
} }
public Task<RedisValue[]> HashGetAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) public Task<RedisValue[]> HashGetAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashGetAsync(ToInner(key), hashFields, flags); return Inner.HashGetAsync(ToInner(key), hashFields, flags);
} }
public Task<RedisValue> HashGetAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) public Task<RedisValue> HashGetAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashGetAsync(ToInner(key), hashField, flags); return Inner.HashGetAsync(ToInner(key), hashField, flags);
} }
public Task<double> HashIncrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) public Task<double> HashIncrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashIncrementAsync(ToInner(key), hashField, value, flags); return Inner.HashIncrementAsync(ToInner(key), hashField, value, flags);
} }
public Task<long> HashIncrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) public Task<long> HashIncrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashIncrementAsync(ToInner(key), hashField, value, flags); return Inner.HashIncrementAsync(ToInner(key), hashField, value, flags);
} }
public Task<RedisValue[]> HashKeysAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<RedisValue[]> HashKeysAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashKeysAsync(ToInner(key), flags); return Inner.HashKeysAsync(ToInner(key), flags);
} }
public Task<long> HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<long> HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashLengthAsync(ToInner(key), flags); return Inner.HashLengthAsync(ToInner(key), flags);
} }
public Task<bool> HashSetAsync(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) public Task<bool> HashSetAsync(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashSetAsync(ToInner(key), hashField, value, when, flags); return Inner.HashSetAsync(ToInner(key), hashField, value, when, flags);
} }
public Task HashSetAsync(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None) public Task HashSetAsync(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashSetAsync(ToInner(key), hashFields, flags); return Inner.HashSetAsync(ToInner(key), hashFields, flags);
} }
public Task<RedisValue[]> HashValuesAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<RedisValue[]> HashValuesAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashValuesAsync(ToInner(key), flags); return Inner.HashValuesAsync(ToInner(key), flags);
} }
public Task<bool> HyperLogLogAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) public Task<bool> HyperLogLogAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HyperLogLogAddAsync(ToInner(key), values, flags); return Inner.HyperLogLogAddAsync(ToInner(key), values, flags);
} }
public Task<bool> HyperLogLogAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public Task<bool> HyperLogLogAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HyperLogLogAddAsync(ToInner(key), value, flags); return Inner.HyperLogLogAddAsync(ToInner(key), value, flags);
} }
public Task<long> HyperLogLogLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<long> HyperLogLogLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HyperLogLogLengthAsync(ToInner(key), flags); return Inner.HyperLogLogLengthAsync(ToInner(key), flags);
} }
public Task<long> HyperLogLogLengthAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) public Task<long> HyperLogLogLengthAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HyperLogLogLengthAsync(ToInner(keys), flags); return Inner.HyperLogLogLengthAsync(ToInner(keys), flags);
} }
public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None) public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HyperLogLogMergeAsync(ToInner(destination), ToInner(sourceKeys), flags); return Inner.HyperLogLogMergeAsync(ToInner(destination), ToInner(sourceKeys), flags);
} }
public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HyperLogLogMergeAsync(ToInner(destination), ToInner(first), ToInner(second), flags); return Inner.HyperLogLogMergeAsync(ToInner(destination), ToInner(first), ToInner(second), flags);
} }
public Task<EndPoint> IdentifyEndpointAsync(RedisKey key = default(RedisKey), CommandFlags flags = CommandFlags.None) public Task<EndPoint> IdentifyEndpointAsync(RedisKey key = default(RedisKey), CommandFlags flags = CommandFlags.None)
{ {
return Inner.IdentifyEndpointAsync(ToInner(key), flags); return Inner.IdentifyEndpointAsync(ToInner(key), flags);
} }
public bool IsConnected(RedisKey key, CommandFlags flags = CommandFlags.None) public bool IsConnected(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.IsConnected(ToInner(key), flags); return Inner.IsConnected(ToInner(key), flags);
} }
public Task<long> KeyDeleteAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) public Task<long> KeyDeleteAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyDeleteAsync(ToInner(keys), flags); return Inner.KeyDeleteAsync(ToInner(keys), flags);
} }
public Task<bool> KeyDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<bool> KeyDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyDeleteAsync(ToInner(key), flags); return Inner.KeyDeleteAsync(ToInner(key), flags);
} }
public Task<byte[]> KeyDumpAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<byte[]> KeyDumpAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyDumpAsync(ToInner(key), flags); return Inner.KeyDumpAsync(ToInner(key), flags);
} }
public Task<bool> KeyExistsAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<bool> KeyExistsAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyExistsAsync(ToInner(key), flags); return Inner.KeyExistsAsync(ToInner(key), flags);
} }
public Task<bool> KeyExpireAsync(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None) public Task<bool> KeyExpireAsync(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyExpireAsync(ToInner(key), expiry, flags); return Inner.KeyExpireAsync(ToInner(key), expiry, flags);
} }
public Task<bool> KeyExpireAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) public Task<bool> KeyExpireAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyExpireAsync(ToInner(key), expiry, flags); return Inner.KeyExpireAsync(ToInner(key), expiry, flags);
} }
public Task KeyMigrateAsync(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None) public Task KeyMigrateAsync(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyMigrateAsync(ToInner(key), toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags); return Inner.KeyMigrateAsync(ToInner(key), toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags);
} }
public Task<bool> KeyMoveAsync(RedisKey key, int database, CommandFlags flags = CommandFlags.None) public Task<bool> KeyMoveAsync(RedisKey key, int database, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyMoveAsync(ToInner(key), database, flags); return Inner.KeyMoveAsync(ToInner(key), database, flags);
} }
public Task<bool> KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<bool> KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyPersistAsync(ToInner(key), flags); return Inner.KeyPersistAsync(ToInner(key), flags);
} }
public Task<RedisKey> KeyRandomAsync(CommandFlags flags = CommandFlags.None) public Task<RedisKey> KeyRandomAsync(CommandFlags flags = CommandFlags.None)
{ {
throw new NotSupportedException("RANDOMKEY is not supported when a key-prefix is specified"); throw new NotSupportedException("RANDOMKEY is not supported when a key-prefix is specified");
} }
public Task<bool> KeyRenameAsync(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) public Task<bool> KeyRenameAsync(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyRenameAsync(ToInner(key), ToInner(newKey), when, flags); return Inner.KeyRenameAsync(ToInner(key), ToInner(newKey), when, flags);
} }
public Task KeyRestoreAsync(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None) public Task KeyRestoreAsync(RedisKey key, byte[] value, TimeSpan? expiry = null, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyRestoreAsync(ToInner(key), value, expiry, flags); return Inner.KeyRestoreAsync(ToInner(key), value, expiry, flags);
} }
public Task<TimeSpan?> KeyTimeToLiveAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<TimeSpan?> KeyTimeToLiveAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyTimeToLiveAsync(ToInner(key), flags); return Inner.KeyTimeToLiveAsync(ToInner(key), flags);
} }
public Task<RedisType> KeyTypeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<RedisType> KeyTypeAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.KeyTypeAsync(ToInner(key), flags); return Inner.KeyTypeAsync(ToInner(key), flags);
} }
public Task<RedisValue> ListGetByIndexAsync(RedisKey key, long index, CommandFlags flags = CommandFlags.None) public Task<RedisValue> ListGetByIndexAsync(RedisKey key, long index, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListGetByIndexAsync(ToInner(key), index, flags); return Inner.ListGetByIndexAsync(ToInner(key), index, flags);
} }
public Task<long> ListInsertAfterAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) public Task<long> ListInsertAfterAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListInsertAfterAsync(ToInner(key), pivot, value, flags); return Inner.ListInsertAfterAsync(ToInner(key), pivot, value, flags);
} }
public Task<long> ListInsertBeforeAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) public Task<long> ListInsertBeforeAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListInsertBeforeAsync(ToInner(key), pivot, value, flags); return Inner.ListInsertBeforeAsync(ToInner(key), pivot, value, flags);
} }
public Task<RedisValue> ListLeftPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<RedisValue> ListLeftPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListLeftPopAsync(ToInner(key), flags); return Inner.ListLeftPopAsync(ToInner(key), flags);
} }
public Task<long> ListLeftPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) public Task<long> ListLeftPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListLeftPushAsync(ToInner(key), values, flags); return Inner.ListLeftPushAsync(ToInner(key), values, flags);
} }
public Task<long> ListLeftPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) public Task<long> ListLeftPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListLeftPushAsync(ToInner(key), value, when, flags); return Inner.ListLeftPushAsync(ToInner(key), value, when, flags);
} }
public Task<long> ListLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<long> ListLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListLengthAsync(ToInner(key), flags); return Inner.ListLengthAsync(ToInner(key), flags);
} }
public Task<RedisValue[]> ListRangeAsync(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None) public Task<RedisValue[]> ListRangeAsync(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListRangeAsync(ToInner(key), start, stop, flags); return Inner.ListRangeAsync(ToInner(key), start, stop, flags);
} }
public Task<long> ListRemoveAsync(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None) public Task<long> ListRemoveAsync(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListRemoveAsync(ToInner(key), value, count, flags); return Inner.ListRemoveAsync(ToInner(key), value, count, flags);
} }
public Task<RedisValue> ListRightPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<RedisValue> ListRightPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListRightPopAsync(ToInner(key), flags); return Inner.ListRightPopAsync(ToInner(key), flags);
} }
public Task<RedisValue> ListRightPopLeftPushAsync(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None) public Task<RedisValue> ListRightPopLeftPushAsync(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListRightPopLeftPushAsync(ToInner(source), ToInner(destination), flags); return Inner.ListRightPopLeftPushAsync(ToInner(source), ToInner(destination), flags);
} }
public Task<long> ListRightPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) public Task<long> ListRightPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListRightPushAsync(ToInner(key), values, flags); return Inner.ListRightPushAsync(ToInner(key), values, flags);
} }
public Task<long> ListRightPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) public Task<long> ListRightPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListRightPushAsync(ToInner(key), value, when, flags); return Inner.ListRightPushAsync(ToInner(key), value, when, flags);
} }
public Task ListSetByIndexAsync(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None) public Task ListSetByIndexAsync(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListSetByIndexAsync(ToInner(key), index, value, flags); return Inner.ListSetByIndexAsync(ToInner(key), index, value, flags);
} }
public Task ListTrimAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) public Task ListTrimAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ListTrimAsync(ToInner(key), start, stop, flags); return Inner.ListTrimAsync(ToInner(key), start, stop, flags);
} }
public Task<bool> LockExtendAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) public Task<bool> LockExtendAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None)
{ {
return Inner.LockExtendAsync(ToInner(key), value, expiry, flags); return Inner.LockExtendAsync(ToInner(key), value, expiry, flags);
} }
public Task<RedisValue> LockQueryAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<RedisValue> LockQueryAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.LockQueryAsync(ToInner(key), flags); return Inner.LockQueryAsync(ToInner(key), flags);
} }
public Task<bool> LockReleaseAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public Task<bool> LockReleaseAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.LockReleaseAsync(ToInner(key), value, flags); return Inner.LockReleaseAsync(ToInner(key), value, flags);
} }
public Task<bool> LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) public Task<bool> LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None)
{ {
return Inner.LockTakeAsync(ToInner(key), value, expiry, flags); return Inner.LockTakeAsync(ToInner(key), value, expiry, flags);
} }
public Task<long> PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) public Task<long> PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None)
{ {
return Inner.PublishAsync(ToInner(channel), message, flags); return Inner.PublishAsync(ToInner(channel), message, flags);
} }
public Task<RedisResult> ExecuteAsync(string command, params object[] args) public Task<RedisResult> ExecuteAsync(string command, params object[] args)
=> Inner.ExecuteAsync(command, ToInner(args), CommandFlags.None); => Inner.ExecuteAsync(command, ToInner(args), CommandFlags.None);
public Task<RedisResult> ExecuteAsync(string command, ICollection<object> args, CommandFlags flags = CommandFlags.None) public Task<RedisResult> ExecuteAsync(string command, ICollection<object> args, CommandFlags flags = CommandFlags.None)
=> Inner.ExecuteAsync(command, ToInner(args), flags); => Inner.ExecuteAsync(command, ToInner(args), flags);
public Task<RedisResult> ScriptEvaluateAsync(byte[] hash, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) public Task<RedisResult> ScriptEvaluateAsync(byte[] hash, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None)
{ {
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
return Inner.ScriptEvaluateAsync(hash, ToInner(keys), values, flags); return Inner.ScriptEvaluateAsync(hash, ToInner(keys), values, flags);
} }
public Task<RedisResult> ScriptEvaluateAsync(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) public Task<RedisResult> ScriptEvaluateAsync(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None)
{ {
// TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those? // TODO: The return value could contain prefixed keys. It might make sense to 'unprefix' those?
return Inner.ScriptEvaluateAsync(script, ToInner(keys), values, flags); return Inner.ScriptEvaluateAsync(script, ToInner(keys), values, flags);
} }
public Task<RedisResult> ScriptEvaluateAsync(LuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None) public Task<RedisResult> ScriptEvaluateAsync(LuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ScriptEvaluateAsync(script, parameters, flags); return Inner.ScriptEvaluateAsync(script, parameters, flags);
} }
public Task<RedisResult> ScriptEvaluateAsync(LoadedLuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None) public Task<RedisResult> ScriptEvaluateAsync(LoadedLuaScript script, object parameters = null, CommandFlags flags = CommandFlags.None)
{ {
return Inner.ScriptEvaluateAsync(script, parameters, flags); return Inner.ScriptEvaluateAsync(script, parameters, flags);
} }
public Task<long> SetAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) public Task<long> SetAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetAddAsync(ToInner(key), values, flags); return Inner.SetAddAsync(ToInner(key), values, flags);
} }
public Task<bool> SetAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public Task<bool> SetAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetAddAsync(ToInner(key), value, flags); return Inner.SetAddAsync(ToInner(key), value, flags);
} }
public Task<long> SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) public Task<long> SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetCombineAndStoreAsync(operation, ToInner(destination), ToInner(keys), flags); return Inner.SetCombineAndStoreAsync(operation, ToInner(destination), ToInner(keys), flags);
} }
public Task<long> SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) public Task<long> SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetCombineAndStoreAsync(operation, ToInner(destination), ToInner(first), ToInner(second), flags); return Inner.SetCombineAndStoreAsync(operation, ToInner(destination), ToInner(first), ToInner(second), flags);
} }
public Task<RedisValue[]> SetCombineAsync(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None) public Task<RedisValue[]> SetCombineAsync(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetCombineAsync(operation, ToInner(keys), flags); return Inner.SetCombineAsync(operation, ToInner(keys), flags);
} }
public Task<RedisValue[]> SetCombineAsync(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) public Task<RedisValue[]> SetCombineAsync(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetCombineAsync(operation, ToInner(first), ToInner(second), flags); return Inner.SetCombineAsync(operation, ToInner(first), ToInner(second), flags);
} }
public Task<bool> SetContainsAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public Task<bool> SetContainsAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetContainsAsync(ToInner(key), value, flags); return Inner.SetContainsAsync(ToInner(key), value, flags);
} }
public Task<long> SetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<long> SetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetLengthAsync(ToInner(key), flags); return Inner.SetLengthAsync(ToInner(key), flags);
} }
public Task<RedisValue[]> SetMembersAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<RedisValue[]> SetMembersAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetMembersAsync(ToInner(key), flags); return Inner.SetMembersAsync(ToInner(key), flags);
} }
public Task<bool> SetMoveAsync(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None) public Task<bool> SetMoveAsync(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetMoveAsync(ToInner(source), ToInner(destination), value, flags); return Inner.SetMoveAsync(ToInner(source), ToInner(destination), value, flags);
} }
public Task<RedisValue> SetPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<RedisValue> SetPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetPopAsync(ToInner(key), flags); return Inner.SetPopAsync(ToInner(key), flags);
} }
public Task<RedisValue> SetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<RedisValue> SetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetRandomMemberAsync(ToInner(key), flags); return Inner.SetRandomMemberAsync(ToInner(key), flags);
} }
public Task<RedisValue[]> SetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) public Task<RedisValue[]> SetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetRandomMembersAsync(ToInner(key), count, flags); return Inner.SetRandomMembersAsync(ToInner(key), count, flags);
} }
public Task<long> SetRemoveAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) public Task<long> SetRemoveAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetRemoveAsync(ToInner(key), values, flags); return Inner.SetRemoveAsync(ToInner(key), values, flags);
} }
public Task<bool> SetRemoveAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public Task<bool> SetRemoveAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SetRemoveAsync(ToInner(key), value, flags); return Inner.SetRemoveAsync(ToInner(key), value, flags);
} }
public Task<long> SortAndStoreAsync(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None) public Task<long> SortAndStoreAsync(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortAndStoreAsync(ToInner(destination), ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags); return Inner.SortAndStoreAsync(ToInner(destination), ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags);
} }
public Task<RedisValue[]> SortAsync(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None) public Task<RedisValue[]> SortAsync(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortAsync(ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags); return Inner.SortAsync(ToInner(key), skip, take, order, sortType, SortByToInner(by), SortGetToInner(get), flags);
} }
public Task<long> SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, CommandFlags flags) public Task<long> SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, CommandFlags flags)
{ {
return Inner.SortedSetAddAsync(ToInner(key), values, flags); return Inner.SortedSetAddAsync(ToInner(key), values, flags);
} }
public Task<long> SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) public Task<long> SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetAddAsync(ToInner(key), values, when, flags); return Inner.SortedSetAddAsync(ToInner(key), values, when, flags);
} }
public Task<bool> SortedSetAddAsync(RedisKey key, RedisValue member, double score, CommandFlags flags) public Task<bool> SortedSetAddAsync(RedisKey key, RedisValue member, double score, CommandFlags flags)
{ {
return Inner.SortedSetAddAsync(ToInner(key), member, score, flags); return Inner.SortedSetAddAsync(ToInner(key), member, score, flags);
} }
public Task<bool> SortedSetAddAsync(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None) public Task<bool> SortedSetAddAsync(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetAddAsync(ToInner(key), member, score, when, flags); return Inner.SortedSetAddAsync(ToInner(key), member, score, when, flags);
} }
public Task<long> SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, double[] weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) public Task<long> SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, double[] weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetCombineAndStoreAsync(operation, ToInner(destination), ToInner(keys), weights, aggregate, flags); return Inner.SortedSetCombineAndStoreAsync(operation, ToInner(destination), ToInner(keys), weights, aggregate, flags);
} }
public Task<long> SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) public Task<long> SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetCombineAndStoreAsync(operation, ToInner(destination), ToInner(first), ToInner(second), aggregate, flags); return Inner.SortedSetCombineAndStoreAsync(operation, ToInner(destination), ToInner(first), ToInner(second), aggregate, flags);
} }
public Task<double> SortedSetDecrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) public Task<double> SortedSetDecrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetDecrementAsync(ToInner(key), member, value, flags); return Inner.SortedSetDecrementAsync(ToInner(key), member, value, flags);
} }
public Task<double> SortedSetIncrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) public Task<double> SortedSetIncrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetIncrementAsync(ToInner(key), member, value, flags); return Inner.SortedSetIncrementAsync(ToInner(key), member, value, flags);
} }
public Task<long> SortedSetLengthAsync(RedisKey key, double min = -1.0 / 0.0, double max = 1.0 / 0.0, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) public Task<long> SortedSetLengthAsync(RedisKey key, double min = -1.0 / 0.0, double max = 1.0 / 0.0, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetLengthAsync(ToInner(key), min, max, exclude, flags); return Inner.SortedSetLengthAsync(ToInner(key), min, max, exclude, flags);
} }
public Task<long> SortedSetLengthByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) public Task<long> SortedSetLengthByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetLengthByValueAsync(ToInner(key), min, max, exclude, flags); return Inner.SortedSetLengthByValueAsync(ToInner(key), min, max, exclude, flags);
} }
public Task<RedisValue[]> SortedSetRangeByRankAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) public Task<RedisValue[]> SortedSetRangeByRankAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRangeByRankAsync(ToInner(key), start, stop, order, flags); return Inner.SortedSetRangeByRankAsync(ToInner(key), start, stop, order, flags);
} }
public Task<SortedSetEntry[]> SortedSetRangeByRankWithScoresAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) public Task<SortedSetEntry[]> SortedSetRangeByRankWithScoresAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRangeByRankWithScoresAsync(ToInner(key), start, stop, order, flags); return Inner.SortedSetRangeByRankWithScoresAsync(ToInner(key), start, stop, order, flags);
} }
public Task<RedisValue[]> SortedSetRangeByScoreAsync(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) public Task<RedisValue[]> SortedSetRangeByScoreAsync(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRangeByScoreAsync(ToInner(key), start, stop, exclude, order, skip, take, flags); return Inner.SortedSetRangeByScoreAsync(ToInner(key), start, stop, exclude, order, skip, take, flags);
} }
public Task<SortedSetEntry[]> SortedSetRangeByScoreWithScoresAsync(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) public Task<SortedSetEntry[]> SortedSetRangeByScoreWithScoresAsync(RedisKey key, double start = -1.0 / 0.0, double stop = 1.0 / 0.0, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRangeByScoreWithScoresAsync(ToInner(key), start, stop, exclude, order, skip, take, flags); return Inner.SortedSetRangeByScoreWithScoresAsync(ToInner(key), start, stop, exclude, order, skip, take, flags);
} }
public Task<RedisValue[]> SortedSetRangeByValueAsync(RedisKey key, RedisValue min = default(RedisValue), RedisValue max = default(RedisValue), Exclude exclude = Exclude.None, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None) public Task<RedisValue[]> SortedSetRangeByValueAsync(RedisKey key, RedisValue min = default(RedisValue), RedisValue max = default(RedisValue), Exclude exclude = Exclude.None, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRangeByValueAsync(ToInner(key), min, max, exclude, skip, take, flags); return Inner.SortedSetRangeByValueAsync(ToInner(key), min, max, exclude, skip, take, flags);
} }
public Task<long?> SortedSetRankAsync(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) public Task<long?> SortedSetRankAsync(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRankAsync(ToInner(key), member, order, flags); return Inner.SortedSetRankAsync(ToInner(key), member, order, flags);
} }
public Task<long> SortedSetRemoveAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) public Task<long> SortedSetRemoveAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRemoveAsync(ToInner(key), members, flags); return Inner.SortedSetRemoveAsync(ToInner(key), members, flags);
} }
public Task<bool> SortedSetRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) public Task<bool> SortedSetRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRemoveAsync(ToInner(key), member, flags); return Inner.SortedSetRemoveAsync(ToInner(key), member, flags);
} }
public Task<long> SortedSetRemoveRangeByRankAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) public Task<long> SortedSetRemoveRangeByRankAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRemoveRangeByRankAsync(ToInner(key), start, stop, flags); return Inner.SortedSetRemoveRangeByRankAsync(ToInner(key), start, stop, flags);
} }
public Task<long> SortedSetRemoveRangeByScoreAsync(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) public Task<long> SortedSetRemoveRangeByScoreAsync(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRemoveRangeByScoreAsync(ToInner(key), start, stop, exclude, flags); return Inner.SortedSetRemoveRangeByScoreAsync(ToInner(key), start, stop, exclude, flags);
} }
public Task<long> SortedSetRemoveRangeByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) public Task<long> SortedSetRemoveRangeByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetRemoveRangeByValueAsync(ToInner(key), min, max, exclude, flags); return Inner.SortedSetRemoveRangeByValueAsync(ToInner(key), min, max, exclude, flags);
} }
public Task<double?> SortedSetScoreAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) public Task<double?> SortedSetScoreAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{ {
return Inner.SortedSetScoreAsync(ToInner(key), member, flags); return Inner.SortedSetScoreAsync(ToInner(key), member, flags);
} }
public Task<long> StringAppendAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public Task<long> StringAppendAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringAppendAsync(ToInner(key), value, flags); return Inner.StringAppendAsync(ToInner(key), value, flags);
} }
public Task<long> StringBitCountAsync(RedisKey key, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None) public Task<long> StringBitCountAsync(RedisKey key, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringBitCountAsync(ToInner(key), start, end, flags); return Inner.StringBitCountAsync(ToInner(key), start, end, flags);
} }
public Task<long> StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) public Task<long> StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringBitOperationAsync(operation, ToInner(destination), ToInner(keys), flags); return Inner.StringBitOperationAsync(operation, ToInner(destination), ToInner(keys), flags);
} }
public Task<long> StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second = default(RedisKey), CommandFlags flags = CommandFlags.None) public Task<long> StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second = default(RedisKey), CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringBitOperationAsync(operation, ToInner(destination), ToInner(first), ToInnerOrDefault(second), flags); return Inner.StringBitOperationAsync(operation, ToInner(destination), ToInner(first), ToInnerOrDefault(second), flags);
} }
public Task<long> StringBitPositionAsync(RedisKey key, bool bit, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None) public Task<long> StringBitPositionAsync(RedisKey key, bool bit, long start = 0, long end = -1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringBitPositionAsync(ToInner(key), bit, start, end, flags); return Inner.StringBitPositionAsync(ToInner(key), bit, start, end, flags);
} }
public Task<double> StringDecrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None) public Task<double> StringDecrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringDecrementAsync(ToInner(key), value, flags); return Inner.StringDecrementAsync(ToInner(key), value, flags);
} }
public Task<long> StringDecrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) public Task<long> StringDecrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringDecrementAsync(ToInner(key), value, flags); return Inner.StringDecrementAsync(ToInner(key), value, flags);
} }
public Task<RedisValue[]> StringGetAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) public Task<RedisValue[]> StringGetAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringGetAsync(ToInner(keys), flags); return Inner.StringGetAsync(ToInner(keys), flags);
} }
public Task<RedisValue> StringGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<RedisValue> StringGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringGetAsync(ToInner(key), flags); return Inner.StringGetAsync(ToInner(key), flags);
} }
public Task<bool> StringGetBitAsync(RedisKey key, long offset, CommandFlags flags = CommandFlags.None) public Task<bool> StringGetBitAsync(RedisKey key, long offset, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringGetBitAsync(ToInner(key), offset, flags); return Inner.StringGetBitAsync(ToInner(key), offset, flags);
} }
public Task<RedisValue> StringGetRangeAsync(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None) public Task<RedisValue> StringGetRangeAsync(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringGetRangeAsync(ToInner(key), start, end, flags); return Inner.StringGetRangeAsync(ToInner(key), start, end, flags);
} }
public Task<RedisValue> StringGetSetAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public Task<RedisValue> StringGetSetAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringGetSetAsync(ToInner(key), value, flags); return Inner.StringGetSetAsync(ToInner(key), value, flags);
} }
public Task<RedisValueWithExpiry> StringGetWithExpiryAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<RedisValueWithExpiry> StringGetWithExpiryAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringGetWithExpiryAsync(ToInner(key), flags); return Inner.StringGetWithExpiryAsync(ToInner(key), flags);
} }
public Task<double> StringIncrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None) public Task<double> StringIncrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringIncrementAsync(ToInner(key), value, flags); return Inner.StringIncrementAsync(ToInner(key), value, flags);
} }
public Task<long> StringIncrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None) public Task<long> StringIncrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringIncrementAsync(ToInner(key), value, flags); return Inner.StringIncrementAsync(ToInner(key), value, flags);
} }
public Task<long> StringLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<long> StringLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringLengthAsync(ToInner(key), flags); return Inner.StringLengthAsync(ToInner(key), flags);
} }
public Task<bool> StringSetAsync(KeyValuePair<RedisKey, RedisValue>[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) public Task<bool> StringSetAsync(KeyValuePair<RedisKey, RedisValue>[] values, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringSetAsync(ToInner(values), when, flags); return Inner.StringSetAsync(ToInner(values), when, flags);
} }
public Task<bool> StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None) public Task<bool> StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringSetAsync(ToInner(key), value, expiry, when, flags); return Inner.StringSetAsync(ToInner(key), value, expiry, when, flags);
} }
public Task<bool> StringSetBitAsync(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None) public Task<bool> StringSetBitAsync(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringSetBitAsync(ToInner(key), offset, bit, flags); return Inner.StringSetBitAsync(ToInner(key), offset, bit, flags);
} }
public Task<RedisValue> StringSetRangeAsync(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) public Task<RedisValue> StringSetRangeAsync(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.StringSetRangeAsync(ToInner(key), offset, value, flags); return Inner.StringSetRangeAsync(ToInner(key), offset, value, flags);
} }
public Task<TimeSpan> PingAsync(CommandFlags flags = CommandFlags.None) public Task<TimeSpan> PingAsync(CommandFlags flags = CommandFlags.None)
{ {
return Inner.PingAsync(flags); return Inner.PingAsync(flags);
} }
public bool TryWait(Task task) public bool TryWait(Task task)
{ {
return Inner.TryWait(task); return Inner.TryWait(task);
} }
public TResult Wait<TResult>(Task<TResult> task) public TResult Wait<TResult>(Task<TResult> task)
{ {
return Inner.Wait(task); return Inner.Wait(task);
} }
public void Wait(Task task) public void Wait(Task task)
{ {
Inner.Wait(task); Inner.Wait(task);
} }
public void WaitAll(params Task[] tasks) public void WaitAll(params Task[] tasks)
{ {
Inner.WaitAll(tasks); Inner.WaitAll(tasks);
} }
#if DEBUG #if DEBUG
public Task<string> ClientGetNameAsync(CommandFlags flags = CommandFlags.None) public Task<string> ClientGetNameAsync(CommandFlags flags = CommandFlags.None)
{ {
return Inner.ClientGetNameAsync(flags); return Inner.ClientGetNameAsync(flags);
} }
#endif #endif
protected internal RedisKey ToInner(RedisKey outer) protected internal RedisKey ToInner(RedisKey outer)
{ {
return RedisKey.WithPrefix(Prefix, outer); return RedisKey.WithPrefix(Prefix, outer);
} }
protected RedisKey ToInnerOrDefault(RedisKey outer) protected RedisKey ToInnerOrDefault(RedisKey outer)
{ {
if (outer == default(RedisKey)) if (outer == default(RedisKey))
{ {
return outer; return outer;
} }
else else
{ {
return ToInner(outer); return ToInner(outer);
} }
} }
protected ICollection<object> ToInner(ICollection<object> args) protected ICollection<object> ToInner(ICollection<object> args)
{ {
if (args?.Any(x => x is RedisKey || x is RedisChannel) == true) if (args?.Any(x => x is RedisKey || x is RedisChannel) == true)
{ {
var withPrefix = new object[args.Count]; var withPrefix = new object[args.Count];
int i = 0; int i = 0;
foreach(var oldArg in args) foreach(var oldArg in args)
{ {
object newArg; object newArg;
if (oldArg is RedisKey) if (oldArg is RedisKey)
{ {
newArg = ToInner((RedisKey)oldArg); newArg = ToInner((RedisKey)oldArg);
} }
else if (oldArg is RedisChannel) else if (oldArg is RedisChannel)
{ {
newArg = ToInner((RedisChannel)oldArg); newArg = ToInner((RedisChannel)oldArg);
} }
else else
{ {
newArg = oldArg; newArg = oldArg;
} }
withPrefix[i++] = newArg; withPrefix[i++] = newArg;
} }
args = withPrefix; args = withPrefix;
} }
return args; return args;
} }
protected RedisKey[] ToInner(RedisKey[] outer) protected RedisKey[] ToInner(RedisKey[] outer)
{ {
if (outer == null || outer.Length == 0) if (outer == null || outer.Length == 0)
{ {
return outer; return outer;
} }
else else
{ {
RedisKey[] inner = new RedisKey[outer.Length]; RedisKey[] inner = new RedisKey[outer.Length];
for (int i = 0; i < outer.Length; ++i) for (int i = 0; i < outer.Length; ++i)
{ {
inner[i] = ToInner(outer[i]); inner[i] = ToInner(outer[i]);
} }
return inner; return inner;
} }
} }
protected KeyValuePair<RedisKey, RedisValue> ToInner(KeyValuePair<RedisKey, RedisValue> outer) protected KeyValuePair<RedisKey, RedisValue> ToInner(KeyValuePair<RedisKey, RedisValue> outer)
{ {
return new KeyValuePair<RedisKey, RedisValue>(ToInner(outer.Key), outer.Value); return new KeyValuePair<RedisKey, RedisValue>(ToInner(outer.Key), outer.Value);
} }
protected KeyValuePair<RedisKey, RedisValue>[] ToInner(KeyValuePair<RedisKey, RedisValue>[] outer) protected KeyValuePair<RedisKey, RedisValue>[] ToInner(KeyValuePair<RedisKey, RedisValue>[] outer)
{ {
if (outer == null || outer.Length == 0) if (outer == null || outer.Length == 0)
{ {
return outer; return outer;
} }
else else
{ {
KeyValuePair<RedisKey, RedisValue>[] inner = new KeyValuePair<RedisKey, RedisValue>[outer.Length]; KeyValuePair<RedisKey, RedisValue>[] inner = new KeyValuePair<RedisKey, RedisValue>[outer.Length];
for (int i = 0; i < outer.Length; ++i) for (int i = 0; i < outer.Length; ++i)
{ {
inner[i] = ToInner(outer[i]); inner[i] = ToInner(outer[i]);
} }
return inner; return inner;
} }
} }
protected RedisValue ToInner(RedisValue outer) protected RedisValue ToInner(RedisValue outer)
{ {
return RedisKey.ConcatenateBytes(Prefix, null, (byte[])outer); return RedisKey.ConcatenateBytes(Prefix, null, (byte[])outer);
} }
protected RedisValue SortByToInner(RedisValue outer) protected RedisValue SortByToInner(RedisValue outer)
{ {
if (outer == "nosort") if (outer == "nosort")
{ {
return outer; return outer;
} }
else else
{ {
return ToInner(outer); return ToInner(outer);
} }
} }
protected RedisValue SortGetToInner(RedisValue outer) protected RedisValue SortGetToInner(RedisValue outer)
{ {
if (outer == "#") if (outer == "#")
{ {
return outer; return outer;
} }
else else
{ {
return ToInner(outer); return ToInner(outer);
} }
} }
protected RedisValue[] SortGetToInner(RedisValue[] outer) protected RedisValue[] SortGetToInner(RedisValue[] outer)
{ {
if (outer == null || outer.Length == 0) if (outer == null || outer.Length == 0)
{ {
return outer; return outer;
} }
else else
{ {
RedisValue[] inner = new RedisValue[outer.Length]; RedisValue[] inner = new RedisValue[outer.Length];
for (int i = 0; i < outer.Length; ++i) for (int i = 0; i < outer.Length; ++i)
{ {
inner[i] = SortGetToInner(outer[i]); inner[i] = SortGetToInner(outer[i]);
} }
return inner; return inner;
} }
} }
protected RedisChannel ToInner(RedisChannel outer) protected RedisChannel ToInner(RedisChannel outer)
{ {
return RedisKey.ConcatenateBytes(Prefix, null, (byte[])outer); return RedisKey.ConcatenateBytes(Prefix, null, (byte[])outer);
} }
private Func<RedisKey, RedisKey> mapFunction; private Func<RedisKey, RedisKey> mapFunction;
protected Func<RedisKey, RedisKey> GetMapFunction() protected Func<RedisKey, RedisKey> GetMapFunction()
{ {
// create as a delegate when first required, then re-use // create as a delegate when first required, then re-use
return mapFunction ?? (mapFunction = new Func<RedisKey, RedisKey>(ToInner)); return mapFunction ?? (mapFunction = new Func<RedisKey, RedisKey>(ToInner));
} }
} }
} }
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
/// <summary> /// <summary>
/// Represents a Lua script that can be executed on Redis. /// Represents a Lua script that can be executed on Redis.
/// ///
/// Unlike normal Redis Lua scripts, LuaScript can have named parameters (prefixed by a @). /// Unlike normal Redis Lua scripts, LuaScript can have named parameters (prefixed by a @).
/// Public fields and properties of the passed in object are treated as parameters. /// Public fields and properties of the passed in object are treated as parameters.
/// ///
/// Parameters of type RedisKey are sent to Redis as KEY (https://redis.io/commands/eval) in addition to arguments, /// Parameters of type RedisKey are sent to Redis as KEY (https://redis.io/commands/eval) in addition to arguments,
/// so as to play nicely with Redis Cluster. /// so as to play nicely with Redis Cluster.
/// ///
/// All members of this class are thread safe. /// All members of this class are thread safe.
/// </summary> /// </summary>
public sealed class LuaScript public sealed class LuaScript
{ {
// Since the mapping of "script text" -> LuaScript doesn't depend on any particular details of // Since the mapping of "script text" -> LuaScript doesn't depend on any particular details of
// the redis connection itself, this cache is global. // the redis connection itself, this cache is global.
private static readonly ConcurrentDictionary<string, WeakReference> Cache = new ConcurrentDictionary<string, WeakReference>(); private static readonly ConcurrentDictionary<string, WeakReference> Cache = new ConcurrentDictionary<string, WeakReference>();
/// <summary> /// <summary>
/// The original Lua script that was used to create this. /// The original Lua script that was used to create this.
/// </summary> /// </summary>
public string OriginalScript { get; } public string OriginalScript { get; }
/// <summary> /// <summary>
/// The Lua script that will actually be sent to Redis for execution. /// The Lua script that will actually be sent to Redis for execution.
/// ///
/// All @-prefixed parameter names have been replaced at this point. /// All @-prefixed parameter names have been replaced at this point.
/// </summary> /// </summary>
public string ExecutableScript { get; } public string ExecutableScript { get; }
// Arguments are in the order they have to passed to the script in // Arguments are in the order they have to passed to the script in
internal string[] Arguments { get; } internal string[] Arguments { get; }
private bool HasArguments => Arguments?.Length > 0; private bool HasArguments => Arguments?.Length > 0;
private readonly Hashtable ParameterMappers; private readonly Hashtable ParameterMappers;
internal LuaScript(string originalScript, string executableScript, string[] arguments) internal LuaScript(string originalScript, string executableScript, string[] arguments)
{ {
OriginalScript = originalScript; OriginalScript = originalScript;
ExecutableScript = executableScript; ExecutableScript = executableScript;
Arguments = arguments; Arguments = arguments;
if (HasArguments) if (HasArguments)
{ {
ParameterMappers = new Hashtable(); ParameterMappers = new Hashtable();
} }
} }
/// <summary> /// <summary>
/// Finalizer, used to prompt cleanups of the script cache when /// Finalizer, used to prompt cleanups of the script cache when
/// a LuaScript reference goes out of scope. /// a LuaScript reference goes out of scope.
/// </summary> /// </summary>
~LuaScript() ~LuaScript()
{ {
try try
{ {
Cache.TryRemove(OriginalScript, out _); Cache.TryRemove(OriginalScript, out _);
} }
catch { } catch { }
} }
/// <summary> /// <summary>
/// Invalidates the internal cache of LuaScript objects. /// Invalidates the internal cache of LuaScript objects.
/// Existing LuaScripts will continue to work, but future calls to LuaScript.Prepare /// Existing LuaScripts will continue to work, but future calls to LuaScript.Prepare
/// return a new LuaScript instance. /// return a new LuaScript instance.
/// </summary> /// </summary>
public static void PurgeCache() => Cache.Clear(); public static void PurgeCache() => Cache.Clear();
/// <summary> /// <summary>
/// Returns the number of cached LuaScripts. /// Returns the number of cached LuaScripts.
/// </summary> /// </summary>
public static int GetCachedScriptCount() => Cache.Count; public static int GetCachedScriptCount() => Cache.Count;
/// <summary> /// <summary>
/// Prepares a Lua script with named parameters to be run against any Redis instance. /// Prepares a Lua script with named parameters to be run against any Redis instance.
/// </summary> /// </summary>
/// <param name="script">The script to prepare.</param> /// <param name="script">The script to prepare.</param>
public static LuaScript Prepare(string script) public static LuaScript Prepare(string script)
{ {
LuaScript ret; LuaScript ret;
if (!Cache.TryGetValue(script, out WeakReference weakRef) || (ret = (LuaScript)weakRef.Target) == null) if (!Cache.TryGetValue(script, out WeakReference weakRef) || (ret = (LuaScript)weakRef.Target) == null)
{ {
ret = ScriptParameterMapper.PrepareScript(script); ret = ScriptParameterMapper.PrepareScript(script);
Cache[script] = new WeakReference(ret); Cache[script] = new WeakReference(ret);
} }
return ret; return ret;
} }
internal void ExtractParameters(object ps, RedisKey? keyPrefix, out RedisKey[] keys, out RedisValue[] args) internal void ExtractParameters(object ps, RedisKey? keyPrefix, out RedisKey[] keys, out RedisValue[] args)
{ {
if (HasArguments) if (HasArguments)
{ {
if (ps == null) throw new ArgumentNullException(nameof(ps), "Script requires parameters"); if (ps == null) throw new ArgumentNullException(nameof(ps), "Script requires parameters");
var psType = ps.GetType(); var psType = ps.GetType();
var mapper = (Func<object, RedisKey?, ScriptParameterMapper.ScriptParameters>)ParameterMappers[psType]; var mapper = (Func<object, RedisKey?, ScriptParameterMapper.ScriptParameters>)ParameterMappers[psType];
if (ps != null && mapper == null) if (ps != null && mapper == null)
{ {
lock (ParameterMappers) lock (ParameterMappers)
{ {
mapper = (Func<object, RedisKey?, ScriptParameterMapper.ScriptParameters>)ParameterMappers[psType]; mapper = (Func<object, RedisKey?, ScriptParameterMapper.ScriptParameters>)ParameterMappers[psType];
if (mapper == null) if (mapper == null)
{ {
if (!ScriptParameterMapper.IsValidParameterHash(psType, this, out string missingMember, out string badMemberType)) if (!ScriptParameterMapper.IsValidParameterHash(psType, this, out string missingMember, out string badMemberType))
{ {
if (missingMember != null) if (missingMember != null)
{ {
throw new ArgumentException("ps", "Expected [" + missingMember + "] to be a field or gettable property on [" + psType.FullName + "]"); throw new ArgumentException("ps", "Expected [" + missingMember + "] to be a field or gettable property on [" + psType.FullName + "]");
} }
throw new ArgumentException("ps", "Expected [" + badMemberType + "] on [" + psType.FullName + "] to be convertable to a RedisValue"); throw new ArgumentException("ps", "Expected [" + badMemberType + "] on [" + psType.FullName + "] to be convertable to a RedisValue");
} }
ParameterMappers[psType] = mapper = ScriptParameterMapper.GetParameterExtractor(psType, this); ParameterMappers[psType] = mapper = ScriptParameterMapper.GetParameterExtractor(psType, this);
} }
} }
} }
var mapped = mapper(ps, keyPrefix); var mapped = mapper(ps, keyPrefix);
keys = mapped.Keys; keys = mapped.Keys;
args = mapped.Arguments; args = mapped.Arguments;
} }
else else
{ {
keys = null; keys = null;
args = null; args = null;
} }
} }
/// <summary> /// <summary>
/// Evaluates this LuaScript against the given database, extracting parameters from the passed in object if any. /// Evaluates this LuaScript against the given database, extracting parameters from the passed in object if any.
/// </summary> /// </summary>
/// <param name="db">The redis databse to evaluate against.</param> /// <param name="db">The redis databse to evaluate against.</param>
/// <param name="ps">The parameter object to use.</param> /// <param name="ps">The parameter object to use.</param>
/// <param name="withKeyPrefix">The key prefix to use, if any.</param> /// <param name="withKeyPrefix">The key prefix to use, if any.</param>
/// <param name="flags">The command flags to use.</param> /// <param name="flags">The command flags to use.</param>
public RedisResult Evaluate(IDatabase db, object ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None) public RedisResult Evaluate(IDatabase db, object ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None)
{ {
ExtractParameters(ps, withKeyPrefix, out RedisKey[] keys, out RedisValue[] args); ExtractParameters(ps, withKeyPrefix, out RedisKey[] keys, out RedisValue[] args);
return db.ScriptEvaluate(ExecutableScript, keys, args, flags); return db.ScriptEvaluate(ExecutableScript, keys, args, flags);
} }
/// <summary> /// <summary>
/// Evaluates this LuaScript against the given database, extracting parameters from the passed in object if any. /// Evaluates this LuaScript against the given database, extracting parameters from the passed in object if any.
/// </summary> /// </summary>
/// <param name="db">The redis databse to evaluate against.</param> /// <param name="db">The redis databse to evaluate against.</param>
/// <param name="ps">The parameter object to use.</param> /// <param name="ps">The parameter object to use.</param>
/// <param name="withKeyPrefix">The key prefix to use, if any.</param> /// <param name="withKeyPrefix">The key prefix to use, if any.</param>
/// <param name="flags">The command flags to use.</param> /// <param name="flags">The command flags to use.</param>
public Task<RedisResult> EvaluateAsync(IDatabaseAsync db, object ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None) public Task<RedisResult> EvaluateAsync(IDatabaseAsync db, object ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None)
{ {
ExtractParameters(ps, withKeyPrefix, out RedisKey[] keys, out RedisValue[] args); ExtractParameters(ps, withKeyPrefix, out RedisKey[] keys, out RedisValue[] args);
return db.ScriptEvaluateAsync(ExecutableScript, keys, args, flags); return db.ScriptEvaluateAsync(ExecutableScript, keys, args, flags);
} }
/// <summary> /// <summary>
/// Loads this LuaScript into the given IServer so it can be run with it's SHA1 hash, instead of /// Loads this LuaScript into the given IServer so it can be run with it's SHA1 hash, instead of
/// passing the full script on each Evaluate or EvaluateAsync call. /// passing the full script on each Evaluate or EvaluateAsync call.
/// ///
/// Note: the FireAndForget command flag cannot be set /// Note: the FireAndForget command flag cannot be set
/// </summary> /// </summary>
/// <param name="server">The server to load the script on.</param> /// <param name="server">The server to load the script on.</param>
/// <param name="flags">The command flags to use.</param> /// <param name="flags">The command flags to use.</param>
public LoadedLuaScript Load(IServer server, CommandFlags flags = CommandFlags.None) public LoadedLuaScript Load(IServer server, CommandFlags flags = CommandFlags.None)
{ {
if ((flags & CommandFlags.FireAndForget) != 0) if ((flags & CommandFlags.FireAndForget) != 0)
{ {
throw new ArgumentOutOfRangeException(nameof(flags), "Loading a script cannot be FireAndForget"); throw new ArgumentOutOfRangeException(nameof(flags), "Loading a script cannot be FireAndForget");
} }
var hash = server.ScriptLoad(ExecutableScript, flags); var hash = server.ScriptLoad(ExecutableScript, flags);
return new LoadedLuaScript(this, hash); return new LoadedLuaScript(this, hash);
} }
/// <summary> /// <summary>
/// Loads this LuaScript into the given IServer so it can be run with it's SHA1 hash, instead of /// Loads this LuaScript into the given IServer so it can be run with it's SHA1 hash, instead of
/// passing the full script on each Evaluate or EvaluateAsync call. /// passing the full script on each Evaluate or EvaluateAsync call.
/// ///
/// Note: the FireAndForget command flag cannot be set /// Note: the FireAndForget command flag cannot be set
/// </summary> /// </summary>
/// <param name="server">The server to load the script on.</param> /// <param name="server">The server to load the script on.</param>
/// <param name="flags">The command flags to use.</param> /// <param name="flags">The command flags to use.</param>
public async Task<LoadedLuaScript> LoadAsync(IServer server, CommandFlags flags = CommandFlags.None) public async Task<LoadedLuaScript> LoadAsync(IServer server, CommandFlags flags = CommandFlags.None)
{ {
if ((flags & CommandFlags.FireAndForget) != 0) if ((flags & CommandFlags.FireAndForget) != 0)
{ {
throw new ArgumentOutOfRangeException(nameof(flags), "Loading a script cannot be FireAndForget"); throw new ArgumentOutOfRangeException(nameof(flags), "Loading a script cannot be FireAndForget");
} }
var hash = await server.ScriptLoadAsync(ExecutableScript, flags).ForAwait(); var hash = await server.ScriptLoadAsync(ExecutableScript, flags).ForAwait();
return new LoadedLuaScript(this, hash); return new LoadedLuaScript(this, hash);
} }
} }
/// <summary> /// <summary>
/// Represents a Lua script that can be executed on Redis. /// Represents a Lua script that can be executed on Redis.
/// ///
/// Unlike LuaScript, LoadedLuaScript sends the hash of it's ExecutableScript to Redis rather than pass /// Unlike LuaScript, LoadedLuaScript sends the hash of it's ExecutableScript to Redis rather than pass
/// the whole script on each call. This requires that the script be loaded into Redis before it is used. /// the whole script on each call. This requires that the script be loaded into Redis before it is used.
/// ///
/// To create a LoadedLuaScript first create a LuaScript via LuaScript.Prepare(string), then /// To create a LoadedLuaScript first create a LuaScript via LuaScript.Prepare(string), then
/// call Load(IServer, CommandFlags) on the returned LuaScript. /// call Load(IServer, CommandFlags) on the returned LuaScript.
/// ///
/// Unlike normal Redis Lua scripts, LoadedLuaScript can have named parameters (prefixed by a @). /// Unlike normal Redis Lua scripts, LoadedLuaScript can have named parameters (prefixed by a @).
/// Public fields and properties of the passed in object are treated as parameters. /// Public fields and properties of the passed in object are treated as parameters.
/// ///
/// Parameters of type RedisKey are sent to Redis as KEY (https://redis.io/commands/eval) in addition to arguments, /// Parameters of type RedisKey are sent to Redis as KEY (https://redis.io/commands/eval) in addition to arguments,
/// so as to play nicely with Redis Cluster. /// so as to play nicely with Redis Cluster.
/// ///
/// All members of this class are thread safe. /// All members of this class are thread safe.
/// </summary> /// </summary>
public sealed class LoadedLuaScript public sealed class LoadedLuaScript
{ {
/// <summary> /// <summary>
/// The original script that was used to create this LoadedLuaScript. /// The original script that was used to create this LoadedLuaScript.
/// </summary> /// </summary>
public string OriginalScript => Original.OriginalScript; public string OriginalScript => Original.OriginalScript;
/// <summary> /// <summary>
/// The script that will actually be sent to Redis for execution. /// The script that will actually be sent to Redis for execution.
/// </summary> /// </summary>
public string ExecutableScript => Original.ExecutableScript; public string ExecutableScript => Original.ExecutableScript;
/// <summary> /// <summary>
/// The SHA1 hash of ExecutableScript. /// The SHA1 hash of ExecutableScript.
/// ///
/// This is sent to Redis instead of ExecutableScript during Evaluate and EvaluateAsync calls. /// This is sent to Redis instead of ExecutableScript during Evaluate and EvaluateAsync calls.
/// </summary> /// </summary>
public byte[] Hash { get; } public byte[] Hash { get; }
// internal for testing purposes only // internal for testing purposes only
internal LuaScript Original; internal LuaScript Original;
internal LoadedLuaScript(LuaScript original, byte[] hash) internal LoadedLuaScript(LuaScript original, byte[] hash)
{ {
Original = original; Original = original;
Hash = hash; Hash = hash;
} }
/// <summary> /// <summary>
/// Evaluates this LoadedLuaScript against the given database, extracting parameters for the passed in object if any. /// Evaluates this LoadedLuaScript against the given database, extracting parameters for the passed in object if any.
/// ///
/// This method sends the SHA1 hash of the ExecutableScript instead of the script itself. If the script has not /// This method sends the SHA1 hash of the ExecutableScript instead of the script itself. If the script has not
/// been loaded into the passed Redis instance it will fail. /// been loaded into the passed Redis instance it will fail.
/// </summary> /// </summary>
/// <param name="db">The redis databse to evaluate against.</param> /// <param name="db">The redis databse to evaluate against.</param>
/// <param name="ps">The parameter object to use.</param> /// <param name="ps">The parameter object to use.</param>
/// <param name="withKeyPrefix">The key prefix to use, if any.</param> /// <param name="withKeyPrefix">The key prefix to use, if any.</param>
/// <param name="flags">The command flags to use.</param> /// <param name="flags">The command flags to use.</param>
public RedisResult Evaluate(IDatabase db, object ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None) public RedisResult Evaluate(IDatabase db, object ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None)
{ {
Original.ExtractParameters(ps, withKeyPrefix, out RedisKey[] keys, out RedisValue[] args); Original.ExtractParameters(ps, withKeyPrefix, out RedisKey[] keys, out RedisValue[] args);
return db.ScriptEvaluate(Hash, keys, args, flags); return db.ScriptEvaluate(Hash, keys, args, flags);
} }
/// <summary> /// <summary>
/// Evaluates this LoadedLuaScript against the given database, extracting parameters for the passed in object if any. /// Evaluates this LoadedLuaScript against the given database, extracting parameters for the passed in object if any.
/// ///
/// This method sends the SHA1 hash of the ExecutableScript instead of the script itself. If the script has not /// This method sends the SHA1 hash of the ExecutableScript instead of the script itself. If the script has not
/// been loaded into the passed Redis instance it will fail. /// been loaded into the passed Redis instance it will fail.
/// </summary> /// </summary>
/// <param name="db">The redis databse to evaluate against.</param> /// <param name="db">The redis databse to evaluate against.</param>
/// <param name="ps">The parameter object to use.</param> /// <param name="ps">The parameter object to use.</param>
/// <param name="withKeyPrefix">The key prefix to use, if any.</param> /// <param name="withKeyPrefix">The key prefix to use, if any.</param>
/// <param name="flags">The command flags to use.</param> /// <param name="flags">The command flags to use.</param>
public Task<RedisResult> EvaluateAsync(IDatabaseAsync db, object ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None) public Task<RedisResult> EvaluateAsync(IDatabaseAsync db, object ps = null, RedisKey? withKeyPrefix = null, CommandFlags flags = CommandFlags.None)
{ {
Original.ExtractParameters(ps, withKeyPrefix, out RedisKey[] keys, out RedisValue[] args); Original.ExtractParameters(ps, withKeyPrefix, out RedisKey[] keys, out RedisValue[] args);
return db.ScriptEvaluateAsync(Hash, keys, args, flags); return db.ScriptEvaluateAsync(Hash, keys, args, flags);
} }
} }
} }
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
/// <summary> /// <summary>
/// Big ol' wrapper around most of the profiling storage logic, 'cause it got too big to just live in ConnectionMultiplexer. /// Big ol' wrapper around most of the profiling storage logic, 'cause it got too big to just live in ConnectionMultiplexer.
/// </summary> /// </summary>
internal sealed class ProfileContextTracker internal sealed class ProfileContextTracker
{ {
/// <summary> /// <summary>
/// Necessary, because WeakReference can't be readily comparable (since the reference is... weak). /// Necessary, because WeakReference can't be readily comparable (since the reference is... weak).
/// ///
/// This lets us detect leaks* with some reasonable confidence, and cleanup periodically. /// This lets us detect leaks* with some reasonable confidence, and cleanup periodically.
/// ///
/// Some calisthenics are done to avoid allocating WeakReferences for no reason, as often /// Some calisthenics are done to avoid allocating WeakReferences for no reason, as often
/// we're just looking up ProfileStorage. /// we're just looking up ProfileStorage.
/// ///
/// * Somebody starts profiling, but for whatever reason never *stops* with a context object /// * Somebody starts profiling, but for whatever reason never *stops* with a context object
/// </summary> /// </summary>
private struct ProfileContextCell : IEquatable<ProfileContextCell> private struct ProfileContextCell : IEquatable<ProfileContextCell>
{ {
// This is a union of (object|WeakReference); if it's a WeakReference // This is a union of (object|WeakReference); if it's a WeakReference
// then we're actually interested in it's Target, otherwise // then we're actually interested in it's Target, otherwise
// we're concerned about the actual value of Reference // we're concerned about the actual value of Reference
private readonly object Reference; private readonly object Reference;
// It is absolutely crucial that this value **never change** once instantiated // It is absolutely crucial that this value **never change** once instantiated
private readonly int HashCode; private readonly int HashCode;
public bool IsContextLeaked => !TryGetTarget(out _); public bool IsContextLeaked => !TryGetTarget(out _);
private ProfileContextCell(object forObj, bool isEphemeral) private ProfileContextCell(object forObj, bool isEphemeral)
{ {
HashCode = forObj.GetHashCode(); HashCode = forObj.GetHashCode();
if (isEphemeral) if (isEphemeral)
{ {
Reference = forObj; Reference = forObj;
} }
else else
{ {
Reference = new WeakReference(forObj, trackResurrection: true); // ughhh, have to handle finalizers Reference = new WeakReference(forObj, trackResurrection: true); // ughhh, have to handle finalizers
} }
} }
/// <summary> /// <summary>
/// Suitable for use as a key into something. /// Suitable for use as a key into something.
/// ///
/// This instance **WILL NOT** keep forObj alive, so it can /// This instance **WILL NOT** keep forObj alive, so it can
/// be copied out of the calling method's scope. /// be copied out of the calling method's scope.
/// </summary> /// </summary>
/// <param name="forObj">The object to get a context for.</param> /// <param name="forObj">The object to get a context for.</param>
public static ProfileContextCell ToStoreUnder(object forObj) => new ProfileContextCell(forObj, isEphemeral: false); public static ProfileContextCell ToStoreUnder(object forObj) => new ProfileContextCell(forObj, isEphemeral: false);
/// <summary> /// <summary>
/// Only suitable for looking up. /// Only suitable for looking up.
/// ///
/// This instance **ABSOLUTELY WILL** keep forObj alive, so this /// This instance **ABSOLUTELY WILL** keep forObj alive, so this
/// had better not be copied into anything outside the scope of the /// had better not be copied into anything outside the scope of the
/// calling method. /// calling method.
/// </summary> /// </summary>
/// <param name="forObj">The object to lookup a context by.</param> /// <param name="forObj">The object to lookup a context by.</param>
public static ProfileContextCell ToLookupBy(object forObj) => new ProfileContextCell(forObj, isEphemeral: true); public static ProfileContextCell ToLookupBy(object forObj) => new ProfileContextCell(forObj, isEphemeral: true);
private bool TryGetTarget(out object target) private bool TryGetTarget(out object target)
{ {
var asWeakRef = Reference as WeakReference; var asWeakRef = Reference as WeakReference;
if (asWeakRef == null) if (asWeakRef == null)
{ {
target = Reference; target = Reference;
return true; return true;
} }
// Do not use IsAlive here, it's race city // Do not use IsAlive here, it's race city
target = asWeakRef.Target; target = asWeakRef.Target;
return target != null; return target != null;
} }
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if (!(obj is ProfileContextCell)) return false; if (!(obj is ProfileContextCell)) return false;
return Equals((ProfileContextCell)obj); return Equals((ProfileContextCell)obj);
} }
public override int GetHashCode() => HashCode; public override int GetHashCode() => HashCode;
public bool Equals(ProfileContextCell other) public bool Equals(ProfileContextCell other)
{ {
if (other.TryGetTarget(out object otherObj) != TryGetTarget(out object thisObj)) return false; if (other.TryGetTarget(out object otherObj) != TryGetTarget(out object thisObj)) return false;
// dead references are equal // dead references are equal
if (thisObj == null) return true; if (thisObj == null) return true;
return thisObj.Equals(otherObj); return thisObj.Equals(otherObj);
} }
} }
// provided so default behavior doesn't do any boxing, for sure // provided so default behavior doesn't do any boxing, for sure
private sealed class ProfileContextCellComparer : IEqualityComparer<ProfileContextCell> private sealed class ProfileContextCellComparer : IEqualityComparer<ProfileContextCell>
{ {
public static readonly ProfileContextCellComparer Singleton = new ProfileContextCellComparer(); public static readonly ProfileContextCellComparer Singleton = new ProfileContextCellComparer();
private ProfileContextCellComparer() { } private ProfileContextCellComparer() { }
public bool Equals(ProfileContextCell x, ProfileContextCell y) public bool Equals(ProfileContextCell x, ProfileContextCell y)
{ {
return x.Equals(y); return x.Equals(y);
} }
public int GetHashCode(ProfileContextCell obj) public int GetHashCode(ProfileContextCell obj)
{ {
return obj.GetHashCode(); return obj.GetHashCode();
} }
} }
private long lastCleanupSweep; private long lastCleanupSweep;
private readonly ConcurrentDictionary<ProfileContextCell, ConcurrentProfileStorageCollection> profiledCommands; private readonly ConcurrentDictionary<ProfileContextCell, ConcurrentProfileStorageCollection> profiledCommands;
public int ContextCount => profiledCommands.Count; public int ContextCount => profiledCommands.Count;
public ProfileContextTracker() public ProfileContextTracker()
{ {
profiledCommands = new ConcurrentDictionary<ProfileContextCell, ConcurrentProfileStorageCollection>(ProfileContextCellComparer.Singleton); profiledCommands = new ConcurrentDictionary<ProfileContextCell, ConcurrentProfileStorageCollection>(ProfileContextCellComparer.Singleton);
lastCleanupSweep = DateTime.UtcNow.Ticks; lastCleanupSweep = DateTime.UtcNow.Ticks;
} }
/// <summary> /// <summary>
/// Registers the passed context with a collection that can be retried with subsequent calls to TryGetValue. /// Registers the passed context with a collection that can be retried with subsequent calls to TryGetValue.
/// ///
/// Returns false if the passed context object is already registered. /// Returns false if the passed context object is already registered.
/// </summary> /// </summary>
/// <param name="ctx">The context to use.</param> /// <param name="ctx">The context to use.</param>
public bool TryCreate(object ctx) public bool TryCreate(object ctx)
{ {
var cell = ProfileContextCell.ToStoreUnder(ctx); var cell = ProfileContextCell.ToStoreUnder(ctx);
// we can't pass this as a delegate, because TryAdd may invoke the factory multiple times, // we can't pass this as a delegate, because TryAdd may invoke the factory multiple times,
// which would lead to over allocation. // which would lead to over allocation.
var storage = ConcurrentProfileStorageCollection.GetOrCreate(); var storage = ConcurrentProfileStorageCollection.GetOrCreate();
return profiledCommands.TryAdd(cell, storage); return profiledCommands.TryAdd(cell, storage);
} }
/// <summary> /// <summary>
/// Returns true and sets val to the tracking collection associated with the given context if the context /// Returns true and sets val to the tracking collection associated with the given context if the context
/// was registered with TryCreate. /// was registered with TryCreate.
/// ///
/// Otherwise returns false and sets val to null. /// Otherwise returns false and sets val to null.
/// </summary> /// </summary>
/// <param name="ctx">The context to get a value for.</param> /// <param name="ctx">The context to get a value for.</param>
/// <param name="val">The collection (if present) for <paramref name="ctx"/>.</param> /// <param name="val">The collection (if present) for <paramref name="ctx"/>.</param>
public bool TryGetValue(object ctx, out ConcurrentProfileStorageCollection val) public bool TryGetValue(object ctx, out ConcurrentProfileStorageCollection val)
{ {
var cell = ProfileContextCell.ToLookupBy(ctx); var cell = ProfileContextCell.ToLookupBy(ctx);
return profiledCommands.TryGetValue(cell, out val); return profiledCommands.TryGetValue(cell, out val);
} }
/// <summary> /// <summary>
/// Removes a context, setting all commands to a (non-thread safe) enumerable of /// Removes a context, setting all commands to a (non-thread safe) enumerable of
/// all the commands attached to that context. /// all the commands attached to that context.
/// ///
/// If the context was never registered, will return false and set commands to null. /// If the context was never registered, will return false and set commands to null.
/// ///
/// Subsequent calls to TryRemove with the same context will return false unless it is /// Subsequent calls to TryRemove with the same context will return false unless it is
/// re-registered with TryCreate. /// re-registered with TryCreate.
/// </summary> /// </summary>
/// <param name="ctx">The context to remove for.</param> /// <param name="ctx">The context to remove for.</param>
/// <param name="commands">The commands to remove.</param> /// <param name="commands">The commands to remove.</param>
public bool TryRemove(object ctx, out ProfiledCommandEnumerable commands) public bool TryRemove(object ctx, out ProfiledCommandEnumerable commands)
{ {
var cell = ProfileContextCell.ToLookupBy(ctx); var cell = ProfileContextCell.ToLookupBy(ctx);
if (!profiledCommands.TryRemove(cell, out ConcurrentProfileStorageCollection storage)) if (!profiledCommands.TryRemove(cell, out ConcurrentProfileStorageCollection storage))
{ {
commands = default(ProfiledCommandEnumerable); commands = default(ProfiledCommandEnumerable);
return false; return false;
} }
commands = storage.EnumerateAndReturnForReuse(); commands = storage.EnumerateAndReturnForReuse();
return true; return true;
} }
/// <summary> /// <summary>
/// If enough time has passed (1 minute) since the last call, this does walk of all contexts /// If enough time has passed (1 minute) since the last call, this does walk of all contexts
/// and removes those that the GC has collected. /// and removes those that the GC has collected.
/// </summary> /// </summary>
public bool TryCleanup() public bool TryCleanup()
{ {
const long SweepEveryTicks = 600000000; // once a minute, tops const long SweepEveryTicks = 600000000; // once a minute, tops
var now = DateTime.UtcNow.Ticks; // resolution on this isn't great, but it's good enough var now = DateTime.UtcNow.Ticks; // resolution on this isn't great, but it's good enough
var last = lastCleanupSweep; var last = lastCleanupSweep;
var since = now - last; var since = now - last;
if (since < SweepEveryTicks) return false; if (since < SweepEveryTicks) return false;
// this is just to keep other threads from wasting time, in theory // this is just to keep other threads from wasting time, in theory
// it'd be perfectly safe for this to run concurrently // it'd be perfectly safe for this to run concurrently
var saw = Interlocked.CompareExchange(ref lastCleanupSweep, now, last); var saw = Interlocked.CompareExchange(ref lastCleanupSweep, now, last);
if (saw != last) return false; if (saw != last) return false;
if (profiledCommands.Count == 0) return false; if (profiledCommands.Count == 0) return false;
using (var e = profiledCommands.GetEnumerator()) using (var e = profiledCommands.GetEnumerator())
{ {
while (e.MoveNext()) while (e.MoveNext())
{ {
var pair = e.Current; var pair = e.Current;
if (pair.Key.IsContextLeaked && profiledCommands.TryRemove(pair.Key, out ConcurrentProfileStorageCollection abandoned)) if (pair.Key.IsContextLeaked && profiledCommands.TryRemove(pair.Key, out ConcurrentProfileStorageCollection abandoned))
{ {
// shove it back in the pool, but don't bother enumerating // shove it back in the pool, but don't bother enumerating
abandoned.ReturnForReuse(); abandoned.ReturnForReuse();
} }
} }
} }
return true; return true;
} }
} }
} }
\ No newline at end of file
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
internal class ProfileStorage : IProfiledCommand internal class ProfileStorage : IProfiledCommand
{ {
#region IProfiledCommand Impl #region IProfiledCommand Impl
public EndPoint EndPoint => Server.EndPoint; public EndPoint EndPoint => Server.EndPoint;
public int Db => Message.Db; public int Db => Message.Db;
public string Command => Message.Command.ToString(); public string Command => Message.Command.ToString();
public CommandFlags Flags => Message.Flags; public CommandFlags Flags => Message.Flags;
public DateTime CommandCreated => MessageCreatedDateTime; public DateTime CommandCreated => MessageCreatedDateTime;
public TimeSpan CreationToEnqueued => TimeSpan.FromTicks(EnqueuedTimeStamp - MessageCreatedTimeStamp); public TimeSpan CreationToEnqueued => TimeSpan.FromTicks(EnqueuedTimeStamp - MessageCreatedTimeStamp);
public TimeSpan EnqueuedToSending => TimeSpan.FromTicks(RequestSentTimeStamp - EnqueuedTimeStamp); public TimeSpan EnqueuedToSending => TimeSpan.FromTicks(RequestSentTimeStamp - EnqueuedTimeStamp);
public TimeSpan SentToResponse => TimeSpan.FromTicks(ResponseReceivedTimeStamp - RequestSentTimeStamp); public TimeSpan SentToResponse => TimeSpan.FromTicks(ResponseReceivedTimeStamp - RequestSentTimeStamp);
public TimeSpan ResponseToCompletion => TimeSpan.FromTicks(CompletedTimeStamp - ResponseReceivedTimeStamp); public TimeSpan ResponseToCompletion => TimeSpan.FromTicks(CompletedTimeStamp - ResponseReceivedTimeStamp);
public TimeSpan ElapsedTime => TimeSpan.FromTicks(CompletedTimeStamp - MessageCreatedTimeStamp); public TimeSpan ElapsedTime => TimeSpan.FromTicks(CompletedTimeStamp - MessageCreatedTimeStamp);
public IProfiledCommand RetransmissionOf => OriginalProfiling; public IProfiledCommand RetransmissionOf => OriginalProfiling;
public RetransmissionReasonType? RetransmissionReason { get; } public RetransmissionReasonType? RetransmissionReason { get; }
#endregion #endregion
public ProfileStorage NextElement { get; set; } public ProfileStorage NextElement { get; set; }
private Message Message; private Message Message;
private readonly ServerEndPoint Server; private readonly ServerEndPoint Server;
private readonly ProfileStorage OriginalProfiling; private readonly ProfileStorage OriginalProfiling;
private DateTime MessageCreatedDateTime; private DateTime MessageCreatedDateTime;
private long MessageCreatedTimeStamp; private long MessageCreatedTimeStamp;
private long EnqueuedTimeStamp; private long EnqueuedTimeStamp;
private long RequestSentTimeStamp; private long RequestSentTimeStamp;
private long ResponseReceivedTimeStamp; private long ResponseReceivedTimeStamp;
private long CompletedTimeStamp; private long CompletedTimeStamp;
private readonly ConcurrentProfileStorageCollection PushToWhenFinished; private readonly ConcurrentProfileStorageCollection PushToWhenFinished;
private ProfileStorage(ConcurrentProfileStorageCollection pushTo, ServerEndPoint server, ProfileStorage resentFor, RetransmissionReasonType? reason) private ProfileStorage(ConcurrentProfileStorageCollection pushTo, ServerEndPoint server, ProfileStorage resentFor, RetransmissionReasonType? reason)
{ {
PushToWhenFinished = pushTo; PushToWhenFinished = pushTo;
OriginalProfiling = resentFor; OriginalProfiling = resentFor;
Server = server; Server = server;
RetransmissionReason = reason; RetransmissionReason = reason;
} }
public static ProfileStorage NewWithContext(ConcurrentProfileStorageCollection pushTo, ServerEndPoint server) public static ProfileStorage NewWithContext(ConcurrentProfileStorageCollection pushTo, ServerEndPoint server)
{ {
return new ProfileStorage(pushTo, server, null, null); return new ProfileStorage(pushTo, server, null, null);
} }
public static ProfileStorage NewAttachedToSameContext(ProfileStorage resentFor, ServerEndPoint server, bool isMoved) public static ProfileStorage NewAttachedToSameContext(ProfileStorage resentFor, ServerEndPoint server, bool isMoved)
{ {
return new ProfileStorage(resentFor.PushToWhenFinished, server, resentFor, isMoved ? RetransmissionReasonType.Moved : RetransmissionReasonType.Ask); return new ProfileStorage(resentFor.PushToWhenFinished, server, resentFor, isMoved ? RetransmissionReasonType.Moved : RetransmissionReasonType.Ask);
} }
public void SetMessage(Message msg) public void SetMessage(Message msg)
{ {
// This method should never be called twice // This method should never be called twice
if (Message != null) throw new InvalidOperationException($"{nameof(SetMessage)} called more than once"); if (Message != null) throw new InvalidOperationException($"{nameof(SetMessage)} called more than once");
Message = msg; Message = msg;
MessageCreatedDateTime = msg.createdDateTime; MessageCreatedDateTime = msg.createdDateTime;
MessageCreatedTimeStamp = msg.createdTimestamp; MessageCreatedTimeStamp = msg.createdTimestamp;
} }
public void SetEnqueued() public void SetEnqueued()
{ {
// This method should never be called twice // This method should never be called twice
if (EnqueuedTimeStamp > 0) throw new InvalidOperationException($"{nameof(SetEnqueued)} called more than once"); if (EnqueuedTimeStamp > 0) throw new InvalidOperationException($"{nameof(SetEnqueued)} called more than once");
EnqueuedTimeStamp = Stopwatch.GetTimestamp(); EnqueuedTimeStamp = Stopwatch.GetTimestamp();
} }
public void SetRequestSent() public void SetRequestSent()
{ {
// This method should never be called twice // This method should never be called twice
if (RequestSentTimeStamp > 0) throw new InvalidOperationException($"{nameof(SetRequestSent)} called more than once"); if (RequestSentTimeStamp > 0) throw new InvalidOperationException($"{nameof(SetRequestSent)} called more than once");
RequestSentTimeStamp = Stopwatch.GetTimestamp(); RequestSentTimeStamp = Stopwatch.GetTimestamp();
} }
public void SetResponseReceived() public void SetResponseReceived()
{ {
if (ResponseReceivedTimeStamp > 0) throw new InvalidOperationException($"{nameof(SetResponseReceived)} called more than once"); if (ResponseReceivedTimeStamp > 0) throw new InvalidOperationException($"{nameof(SetResponseReceived)} called more than once");
ResponseReceivedTimeStamp = Stopwatch.GetTimestamp(); ResponseReceivedTimeStamp = Stopwatch.GetTimestamp();
} }
public void SetCompleted() public void SetCompleted()
{ {
// this method can be called multiple times, depending on how the task completed (async vs not) // this method can be called multiple times, depending on how the task completed (async vs not)
// so we actually have to guard against it. // so we actually have to guard against it.
var now = Stopwatch.GetTimestamp(); var now = Stopwatch.GetTimestamp();
var oldVal = Interlocked.CompareExchange(ref CompletedTimeStamp, now, 0); var oldVal = Interlocked.CompareExchange(ref CompletedTimeStamp, now, 0);
// second call // second call
if (oldVal != 0) return; if (oldVal != 0) return;
// only push on the first call, no dupes! // only push on the first call, no dupes!
PushToWhenFinished.Add(this); PushToWhenFinished.Add(this);
} }
public override string ToString() public override string ToString()
{ {
return return
$@"EndPoint = {EndPoint} $@"EndPoint = {EndPoint}
Db = {Db} Db = {Db}
Command = {Command} Command = {Command}
CommandCreated = {CommandCreated:u} CommandCreated = {CommandCreated:u}
CreationToEnqueued = {CreationToEnqueued} CreationToEnqueued = {CreationToEnqueued}
EnqueuedToSending = {EnqueuedToSending} EnqueuedToSending = {EnqueuedToSending}
SentToResponse = {SentToResponse} SentToResponse = {SentToResponse}
ResponseToCompletion = {ResponseToCompletion} ResponseToCompletion = {ResponseToCompletion}
ElapsedTime = {ElapsedTime} ElapsedTime = {ElapsedTime}
Flags = {Flags} Flags = {Flags}
RetransmissionOf = ({RetransmissionOf})"; RetransmissionOf = ({RetransmissionOf})";
} }
} }
} }
using System; using System;
#if NETSTANDARD1_5 #if NETSTANDARD1_5
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
#endif #endif
using System.Text; using System.Text;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
/// <summary> /// <summary>
/// Represents values that can be stored in redis /// Represents values that can be stored in 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 = new RedisValue[0];
private static readonly byte[] EmptyByteArr = new byte[0]; private static readonly byte[] EmptyByteArr = new byte[0];
private static readonly byte[] IntegerSentinel = new byte[0]; private static readonly byte[] IntegerSentinel = new byte[0];
private readonly byte[] valueBlob; private readonly byte[] valueBlob;
private readonly long valueInt64; private readonly long valueInt64;
// 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 valueInt64, byte[] valueBlob)
{ {
this.valueInt64 = valueInt64; this.valueInt64 = valueInt64;
this.valueBlob = valueBlob; this.valueBlob = valueBlob;
} }
/// <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, EmptyByteArr);
/// <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, 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 => valueBlob == IntegerSentinel;
/// <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 => valueBlob == 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 => valueBlob == null || (valueBlob.Length == 0 && !(valueBlob == IntegerSentinel));
/// <summary> /// <summary>
/// Indicates whether the value is greater than zero-length /// Indicates whether the value is greater than zero-length
/// </summary> /// </summary>
public bool HasValue => valueBlob?.Length > 0; public bool HasValue => valueBlob?.Length > 0;
/// <summary> /// <summary>
/// Indicates whether two RedisValue values are equivalent /// Indicates whether two RedisValue values are equivalent
/// </summary> /// </summary>
/// <param name="x">The first <see cref="RedisValue"/> to compare.</param> /// <param name="x">The first <see cref="RedisValue"/> to compare.</param>
/// <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);
/// <summary> /// <summary>
/// Indicates whether two RedisValue values are equivalent /// Indicates whether two RedisValue values are equivalent
/// </summary> /// </summary>
/// <param name="x">The first <see cref="RedisValue"/> to compare.</param> /// <param name="x">The first <see cref="RedisValue"/> to compare.</param>
/// <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; if (x.valueBlob == null) return y.valueBlob == null;
if (x.valueBlob == IntegerSentinel) if (x.valueBlob == IntegerSentinel)
{ {
if (y.valueBlob == IntegerSentinel) if (y.valueBlob == IntegerSentinel)
{ {
return x.valueInt64 == y.valueInt64; return x.valueInt64 == y.valueInt64;
} }
else else
{ {
return Equals((byte[])x, (byte[])y); return Equals((byte[])x, (byte[])y);
} }
} }
else if (y.valueBlob == IntegerSentinel) else if (y.valueBlob == IntegerSentinel)
{ {
return Equals((byte[])x, (byte[])y); return Equals((byte[])x, (byte[])y);
} }
return Equals(x.valueBlob, y.valueBlob); return Equals(x.valueBlob, y.valueBlob);
} }
/// <summary> /// <summary>
/// See Object.Equals() /// See Object.Equals()
/// </summary> /// </summary>
/// <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 valueBlob == null;
if (obj is RedisValue) if (obj is RedisValue)
{ {
return Equals((RedisValue)obj); return Equals((RedisValue)obj);
} }
if (obj is string) if (obj is string)
{ {
return (string)obj == (string)this; return (string)obj == (string)this;
} }
if (obj is byte[]) if (obj is byte[])
{ {
return Equals((byte[])obj, (byte[])this); return Equals((byte[])obj, (byte[])this);
} }
if (obj is long) if (obj is long)
{ {
return (long)obj == (long)this; return (long)obj == (long)this;
} }
if (obj is int) if (obj is int)
{ {
return (int)obj == (int)this; return (int)obj == (int)this;
} }
return false; return false;
} }
/// <summary> /// <summary>
/// Indicates whether two RedisValue values are equivalent /// Indicates whether two RedisValue values are equivalent
/// </summary> /// </summary>
/// <param name="other">The <see cref="RedisValue"/> to compare to.</param> /// <param name="other">The <see cref="RedisValue"/> to compare to.</param>
public bool Equals(RedisValue other) => this == other; public bool Equals(RedisValue other) => this == other;
/// <summary> /// <summary>
/// See Object.GetHashCode() /// See Object.GetHashCode()
/// </summary> /// </summary>
public override int GetHashCode() public override int GetHashCode()
{ {
if (valueBlob == IntegerSentinel) return valueInt64.GetHashCode(); if (valueBlob == IntegerSentinel) return valueInt64.GetHashCode();
if (valueBlob == null) return -1; if (valueBlob == null) return -1;
return GetHashCode(valueBlob); return GetHashCode(valueBlob);
} }
/// <summary> /// <summary>
/// Returns a string representation of the value /// Returns a string representation of the value
/// </summary> /// </summary>
public override string ToString() => (string)this; public override string ToString() => (string)this;
internal static unsafe bool Equals(byte[] x, byte[] y) internal static unsafe bool Equals(byte[] x, byte[] y)
{ {
if ((object)x == (object)y) return true; // ref equals if ((object)x == (object)y) return true; // ref equals
if (x == null || y == null) return false; if (x == null || y == null) return false;
int len = x.Length; int len = x.Length;
if (len != y.Length) return false; if (len != y.Length) return false;
int octets = len / 8, spare = len % 8; int octets = len / 8, spare = len % 8;
fixed (byte* x8 = x, y8 = y) fixed (byte* x8 = x, y8 = y)
{ {
long* x64 = (long*)x8, y64 = (long*)y8; long* x64 = (long*)x8, y64 = (long*)y8;
for (int i = 0; i < octets; i++) for (int i = 0; i < octets; i++)
{ {
if (x64[i] != y64[i]) return false; if (x64[i] != y64[i]) return false;
} }
int offset = len - spare; int offset = len - spare;
while (spare-- != 0) while (spare-- != 0)
{ {
if (x8[offset] != y8[offset++]) return false; if (x8[offset] != y8[offset++]) return false;
} }
} }
return true; return true;
} }
internal static unsafe int GetHashCode(byte[] value) internal static unsafe int GetHashCode(byte[] value)
{ {
unchecked unchecked
{ {
if (value == null) return -1; if (value == null) return -1;
int len = value.Length; int len = value.Length;
if (len == 0) return 0; if (len == 0) return 0;
int octets = len / 8, spare = len % 8; int octets = len / 8, spare = len % 8;
int acc = 728271210; int acc = 728271210;
fixed (byte* ptr8 = value) fixed (byte* ptr8 = value)
{ {
long* ptr64 = (long*)ptr8; long* ptr64 = (long*)ptr8;
for (int i = 0; i < octets; i++) for (int i = 0; i < octets; i++)
{ {
long val = ptr64[i]; long val = ptr64[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 offset = len - spare;
while (spare-- != 0) while (spare-- != 0)
{ {
acc = (((acc << 5) + acc) ^ ptr8[offset++]); acc = (((acc << 5) + acc) ^ ptr8[offset++]);
} }
} }
return acc; return acc;
} }
} }
internal static bool TryParseInt64(byte[] value, int offset, int count, out long result) internal static bool TryParseInt64(byte[] value, int offset, int count, out long result)
{ {
result = 0; result = 0;
if (value == null || count <= 0) return false; if (value == null || count <= 0) return false;
checked checked
{ {
int max = offset + count; int max = offset + count;
if (value[offset] == '-') if (value[offset] == '-')
{ {
for (int i = offset + 1; i < max; i++) for (int i = offset + 1; i < max; i++)
{ {
var b = value[i]; var b = value[i];
if (b < '0' || b > '9') return false; if (b < '0' || b > '9') return false;
result = (result * 10) - (b - '0'); result = (result * 10) - (b - '0');
} }
return true; return true;
} }
else else
{ {
for (int i = offset; i < max; i++) for (int i = offset; i < max; i++)
{ {
var b = value[i]; var b = value[i];
if (b < '0' || b > '9') return false; if (b < '0' || b > '9') return false;
result = (result * 10) + (b - '0'); result = (result * 10) + (b - '0');
} }
return true; return true;
} }
} }
} }
internal void AssertNotNull() internal void AssertNotNull()
{ {
if (IsNull) throw new ArgumentException("A null value is not valid in this context"); if (IsNull) throw new ArgumentException("A null value is not valid in this context");
} }
private enum CompareType private enum CompareType
{ {
Null, Int64, Double, Raw Null, Int64, Double, Raw
} }
private CompareType ResolveType(out long i64, out double r8) private CompareType ResolveType(out long i64, out double r8)
{ {
byte[] blob = valueBlob; byte[] blob = valueBlob;
if (blob == IntegerSentinel) if (blob == IntegerSentinel)
{ {
i64 = valueInt64; i64 = valueInt64;
r8 = default(double); r8 = default(double);
return CompareType.Int64; return CompareType.Int64;
} }
if (blob == null) if (blob == null)
{ {
i64 = default(long); i64 = default(long);
r8 = default(double); r8 = default(double);
return CompareType.Null; return CompareType.Null;
} }
if (TryParseInt64(blob, 0, blob.Length, out i64)) if (TryParseInt64(blob, 0, blob.Length, out i64))
{ {
r8 = default(double); r8 = default(double);
return CompareType.Int64; return CompareType.Int64;
} }
if (TryParseDouble(blob, out r8)) if (TryParseDouble(blob, out r8))
{ {
i64 = default(long); i64 = default(long);
return CompareType.Double; return CompareType.Double;
} }
i64 = default(long); i64 = default(long);
r8 = default(double); r8 = default(double);
return CompareType.Raw; return CompareType.Raw;
} }
/// <summary> /// <summary>
/// Compare against a RedisValue for relative order /// Compare against a RedisValue for relative order
/// </summary> /// </summary>
/// <param name="other">The other <see cref="RedisValue"/> to compare.</param> /// <param name="other">The other <see cref="RedisValue"/> to compare.</param>
public int CompareTo(RedisValue other) public int CompareTo(RedisValue other)
{ {
try try
{ {
CompareType thisType = ResolveType(out long thisInt64, out double thisDouble), CompareType thisType = ResolveType(out long thisInt64, out double thisDouble),
otherType = other.ResolveType(out long otherInt64, out double otherDouble); otherType = other.ResolveType(out long otherInt64, out double otherDouble);
if (thisType == CompareType.Null) if (thisType == CompareType.Null)
{ {
return otherType == CompareType.Null ? 0 : -1; return otherType == CompareType.Null ? 0 : -1;
} }
if (otherType == CompareType.Null) if (otherType == CompareType.Null)
{ {
return 1; return 1;
} }
if (thisType == CompareType.Int64) if (thisType == CompareType.Int64)
{ {
if (otherType == CompareType.Int64) return thisInt64.CompareTo(otherInt64); if (otherType == CompareType.Int64) return thisInt64.CompareTo(otherInt64);
if (otherType == CompareType.Double) return ((double)thisInt64).CompareTo(otherDouble); if (otherType == CompareType.Double) return ((double)thisInt64).CompareTo(otherDouble);
} }
else if (thisType == CompareType.Double) else if (thisType == CompareType.Double)
{ {
if (otherType == CompareType.Int64) return thisDouble.CompareTo((double)otherInt64); if (otherType == CompareType.Int64) return thisDouble.CompareTo((double)otherInt64);
if (otherType == CompareType.Double) return thisDouble.CompareTo(otherDouble); if (otherType == CompareType.Double) return thisDouble.CompareTo(otherDouble);
} }
// otherwise, compare as strings // otherwise, compare as strings
#if NETSTANDARD1_5 #if NETSTANDARD1_5
var compareInfo = System.Globalization.CultureInfo.InvariantCulture.CompareInfo; var compareInfo = System.Globalization.CultureInfo.InvariantCulture.CompareInfo;
return compareInfo.Compare((string)this, (string)other, System.Globalization.CompareOptions.Ordinal); return compareInfo.Compare((string)this, (string)other, System.Globalization.CompareOptions.Ordinal);
#else #else
return StringComparer.InvariantCulture.Compare((string)this, (string)other); return StringComparer.InvariantCulture.Compare((string)this, (string)other);
#endif #endif
} }
catch (Exception ex) catch (Exception ex)
{ {
ConnectionMultiplexer.TraceWithoutContext(ex.Message); ConnectionMultiplexer.TraceWithoutContext(ex.Message);
} }
// if all else fails, consider equivalent // if all else fails, consider equivalent
return 0; return 0;
} }
int IComparable.CompareTo(object obj) int IComparable.CompareTo(object obj)
{ {
if (obj is RedisValue) return CompareTo((RedisValue)obj); if (obj is RedisValue) return CompareTo((RedisValue)obj);
if (obj is long) return CompareTo((RedisValue)(long)obj); if (obj is long) return CompareTo((RedisValue)(long)obj);
if (obj is double) return CompareTo((RedisValue)(double)obj); if (obj is double) return CompareTo((RedisValue)(double)obj);
if (obj is string) return CompareTo((RedisValue)(string)obj); if (obj is string) return CompareTo((RedisValue)(string)obj);
if (obj is byte[]) return CompareTo((RedisValue)(byte[])obj); if (obj is byte[]) return CompareTo((RedisValue)(byte[])obj);
if (obj is bool) return CompareTo((RedisValue)(bool)obj); if (obj is bool) return CompareTo((RedisValue)(bool)obj);
return -1; return -1;
} }
/// <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, IntegerSentinel);
/// <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}"/>.
/// </summary> /// </summary>
/// <param name="value">The <see cref="T:Nullable{int}"/> to convert to a <see cref="RedisValue"/>.</param> /// <param name="value">The <see cref="T:Nullable{int}"/> to convert to a <see cref="RedisValue"/>.</param>
public static implicit operator RedisValue(int? value) => value == null ? Null : (RedisValue)value.GetValueOrDefault(); public static implicit operator RedisValue(int? value) => value == null ? Null : (RedisValue)value.GetValueOrDefault();
/// <summary> /// <summary>
/// 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, IntegerSentinel);
/// <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}"/>.
/// </summary> /// </summary>
/// <param name="value">The <see cref="T:Nullable{long}"/> to convert to a <see cref="RedisValue"/>.</param> /// <param name="value">The <see cref="T:Nullable{long}"/> to convert to a <see cref="RedisValue"/>.</param>
public static implicit operator RedisValue(long? value) => value == null ? Null : (RedisValue)value.GetValueOrDefault(); public static implicit operator RedisValue(long? value) => value == null ? Null : (RedisValue)value.GetValueOrDefault();
/// <summary> /// <summary>
/// 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) => Format.ToString(value);
/// <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}"/>.
/// </summary> /// </summary>
/// <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> /// <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; byte[] blob;
if (value == null) blob = null; if (value == null) blob = null;
else if (value.Length == 0) blob = EmptyByteArr; else if (value.Length == 0) blob = EmptyByteArr;
else blob = Encoding.UTF8.GetBytes(value); else blob = Encoding.UTF8.GetBytes(value);
return new RedisValue(0, blob); 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; byte[] blob;
if (value == null) blob = null; if (value == null) blob = null;
else if (value.Length == 0) blob = EmptyByteArr; else if (value.Length == 0) blob = EmptyByteArr;
else blob = value; else blob = value;
return new RedisValue(0, blob); return new RedisValue(0, blob);
} }
internal static RedisValue Parse(object obj) internal static RedisValue Parse(object obj)
{ {
if (obj == null) return RedisValue.Null; if (obj == null) return RedisValue.Null;
if (obj is RedisValue) return (RedisValue)obj; if (obj is RedisValue) return (RedisValue)obj;
if (obj is string) return (RedisValue)(string)obj; if (obj is string) return (RedisValue)(string)obj;
if (obj is int) return (RedisValue)(int)obj; if (obj is int) return (RedisValue)(int)obj;
if (obj is double) return (RedisValue)(double)obj; if (obj is double) return (RedisValue)(double)obj;
if (obj is byte[]) return (RedisValue)(byte[])obj; if (obj is byte[]) return (RedisValue)(byte[])obj;
if (obj is bool) return (RedisValue)(bool)obj; if (obj is bool) return (RedisValue)(bool)obj;
if (obj is long) return (RedisValue)(long)obj; if (obj is long) return (RedisValue)(long)obj;
if (obj is float) return (RedisValue)(float)obj; if (obj is float) return (RedisValue)(float)obj;
throw new InvalidOperationException("Unable to format type for redis: " + obj.GetType().FullName); 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, IntegerSentinel);
/// <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}"/>.
/// </summary> /// </summary>
/// <param name="value">The <see cref="T:Nullable{bool}"/> to convert to a <see cref="RedisValue"/>.</param> /// <param name="value">The <see cref="T:Nullable{bool}"/> to convert to a <see cref="RedisValue"/>.</param>
public static implicit operator RedisValue(bool? value) => value == null ? Null : (RedisValue)value.GetValueOrDefault(); public static implicit operator RedisValue(bool? value) => value == null ? Null : (RedisValue)value.GetValueOrDefault();
/// <summary> /// <summary>
/// Converts a <see cref="RedisValue"/> to a <see cref="bool"/>. /// Converts a <see cref="RedisValue"/> to a <see cref="bool"/>.
/// </summary> /// </summary>
/// <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)
{ {
switch ((long)value) switch ((long)value)
{ {
case 0: return false; case 0: return false;
case 1: return true; case 1: return true;
default: throw new InvalidCastException(); default: throw new InvalidCastException();
} }
} }
/// <summary> /// <summary>
/// Converts a <see cref="RedisValue"/> to a <see cref="int"/>. /// Converts a <see cref="RedisValue"/> to a <see cref="int"/>.
/// </summary> /// </summary>
/// <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)
{ {
checked checked
{ {
return (int)(long)value; return (int)(long)value;
} }
} }
/// <summary> /// <summary>
/// Converts a <see cref="RedisValue"/> to a <see cref="long"/>. /// Converts a <see cref="RedisValue"/> to a <see cref="long"/>.
/// </summary> /// </summary>
/// <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; var blob = value.valueBlob;
if (blob == IntegerSentinel) return value.valueInt64; 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") if (blob == null) return 0; // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr")
if (TryParseInt64(blob, 0, blob.Length, out long i64)) return i64; if (TryParseInt64(blob, 0, blob.Length, out long i64)) return i64;
throw new InvalidCastException(); throw new InvalidCastException();
} }
/// <summary> /// <summary>
/// Converts a <see cref="RedisValue"/> to a <see cref="double"/>. /// Converts a <see cref="RedisValue"/> to a <see cref="double"/>.
/// </summary> /// </summary>
/// <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; var blob = value.valueBlob;
if (blob == IntegerSentinel) return value.valueInt64; 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") if (blob == 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; if (TryParseDouble(blob, out double r8)) return r8;
throw new InvalidCastException(); throw new InvalidCastException();
} }
private static bool TryParseDouble(byte[] blob, out double value) private static bool TryParseDouble(byte[] blob, out double value)
{ {
// simple integer? // simple integer?
if (blob.Length == 1 && blob[0] >= '0' && blob[0] <= '9') if (blob.Length == 1 && blob[0] >= '0' && blob[0] <= '9')
{ {
value = blob[0] - '0'; value = blob[0] - '0';
return true; return true;
} }
return Format.TryParseDouble(Encoding.UTF8.GetString(blob), out value); return Format.TryParseDouble(Encoding.UTF8.GetString(blob), out value);
} }
/// <summary> /// <summary>
/// Converts the <see cref="RedisValue"/> to a <see cref="T:Nullable{double}"/>. /// Converts the <see cref="RedisValue"/> to a <see cref="T:Nullable{double}"/>.
/// </summary> /// </summary>
/// <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.valueBlob == null) return null;
return (double)value; return (double)value;
} }
/// <summary> /// <summary>
/// Converts the <see cref="RedisValue"/> to a <see cref="T:Nullable{long}"/>. /// Converts the <see cref="RedisValue"/> to a <see cref="T:Nullable{long}"/>.
/// </summary> /// </summary>
/// <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.valueBlob == null) return null;
return (long)value; return (long)value;
} }
/// <summary> /// <summary>
/// Converts the <see cref="RedisValue"/> to a <see cref="T:Nullable{int}"/>. /// Converts the <see cref="RedisValue"/> to a <see cref="T:Nullable{int}"/>.
/// </summary> /// </summary>
/// <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.valueBlob == null) return null;
return (int)value; return (int)value;
} }
/// <summary> /// <summary>
/// Converts the <see cref="RedisValue"/> to a <see cref="T:Nullable{bool}"/>. /// Converts the <see cref="RedisValue"/> to a <see cref="T:Nullable{bool}"/>.
/// </summary> /// </summary>
/// <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.valueBlob == null) return null;
return (bool)value; return (bool)value;
} }
/// <summary> /// <summary>
/// Converts a <see cref="RedisValue"/> to a <see cref="string"/>. /// Converts a <see cref="RedisValue"/> to a <see cref="string"/>.
/// </summary> /// </summary>
/// <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; var valueBlob = value.valueBlob;
if (valueBlob == IntegerSentinel) if (valueBlob == IntegerSentinel)
return Format.ToString(value.valueInt64); return Format.ToString(value.valueInt64);
if (valueBlob == null) return null; if (valueBlob == null) return null;
if (valueBlob.Length == 0) return ""; if (valueBlob.Length == 0) return "";
if (valueBlob.Length == 2 && valueBlob[0] == (byte)'O' && valueBlob[1] == (byte)'K') if (valueBlob.Length == 2 && valueBlob[0] == (byte)'O' && valueBlob[1] == (byte)'K')
{ {
return "OK"; // special case for +OK status results from modules return "OK"; // special case for +OK status results from modules
} }
try try
{ {
return Encoding.UTF8.GetString(valueBlob); return Encoding.UTF8.GetString(valueBlob);
} }
catch catch
{ {
return BitConverter.ToString(valueBlob); return BitConverter.ToString(valueBlob);
} }
} }
/// <summary> /// <summary>
/// Converts a <see cref="RedisValue"/> to a <see cref="T:byte[]"/>. /// Converts a <see cref="RedisValue"/> to a <see cref="T:byte[]"/>.
/// </summary> /// </summary>
/// <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; var valueBlob = value.valueBlob;
if (valueBlob == IntegerSentinel) if (valueBlob == IntegerSentinel)
{ {
return Encoding.UTF8.GetBytes(Format.ToString(value.valueInt64)); return Encoding.UTF8.GetBytes(Format.ToString(value.valueInt64));
} }
return valueBlob; return valueBlob;
} }
TypeCode IConvertible.GetTypeCode() => TypeCode.Object; TypeCode IConvertible.GetTypeCode() => TypeCode.Object;
bool IConvertible.ToBoolean(IFormatProvider provider) => (bool)this; bool IConvertible.ToBoolean(IFormatProvider provider) => (bool)this;
byte IConvertible.ToByte(IFormatProvider provider) => (byte)this; byte IConvertible.ToByte(IFormatProvider provider) => (byte)this;
char IConvertible.ToChar(IFormatProvider provider) => (char)this; char IConvertible.ToChar(IFormatProvider provider) => (char)this;
DateTime IConvertible.ToDateTime(IFormatProvider provider) => DateTime.Parse((string)this, provider); DateTime IConvertible.ToDateTime(IFormatProvider provider) => DateTime.Parse((string)this, provider);
decimal IConvertible.ToDecimal(IFormatProvider provider) => (decimal)this; decimal IConvertible.ToDecimal(IFormatProvider provider) => (decimal)this;
double IConvertible.ToDouble(IFormatProvider provider) => (double)this; double IConvertible.ToDouble(IFormatProvider provider) => (double)this;
short IConvertible.ToInt16(IFormatProvider provider) => (short)this; short IConvertible.ToInt16(IFormatProvider provider) => (short)this;
int IConvertible.ToInt32(IFormatProvider provider) => (int)this; int IConvertible.ToInt32(IFormatProvider provider) => (int)this;
long IConvertible.ToInt64(IFormatProvider provider) => (long)this; long IConvertible.ToInt64(IFormatProvider provider) => (long)this;
sbyte IConvertible.ToSByte(IFormatProvider provider) => (sbyte)this; sbyte IConvertible.ToSByte(IFormatProvider provider) => (sbyte)this;
float IConvertible.ToSingle(IFormatProvider provider) => (float)this; float IConvertible.ToSingle(IFormatProvider provider) => (float)this;
string IConvertible.ToString(IFormatProvider provider) => (string)this; string IConvertible.ToString(IFormatProvider provider) => (string)this;
object IConvertible.ToType(Type conversionType, IFormatProvider provider) 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(RedisValue)) return this; if (conversionType == typeof(RedisValue)) return this;
switch (conversionType.GetTypeCode()) switch (conversionType.GetTypeCode())
{ {
case TypeCode.Boolean: return (bool)this; case TypeCode.Boolean: return (bool)this;
case TypeCode.Byte: return (byte)this; case TypeCode.Byte: return (byte)this;
case TypeCode.Char: return (char)this; case TypeCode.Char: return (char)this;
case TypeCode.DateTime: return DateTime.Parse((string)this, provider); case TypeCode.DateTime: return DateTime.Parse((string)this, provider);
case TypeCode.Decimal: return (decimal)this; case TypeCode.Decimal: return (decimal)this;
case TypeCode.Double: return (double)this; case TypeCode.Double: return (double)this;
case TypeCode.Int16: return (short)this; case TypeCode.Int16: return (short)this;
case TypeCode.Int32: return (int)this; case TypeCode.Int32: return (int)this;
case TypeCode.Int64: return (long)this; case TypeCode.Int64: return (long)this;
case TypeCode.SByte: return (sbyte)this; case TypeCode.SByte: return (sbyte)this;
case TypeCode.Single: return (float)this; case TypeCode.Single: return (float)this;
case TypeCode.String: return (string)this; case TypeCode.String: return (string)this;
case TypeCode.UInt16: return (ushort)this; case TypeCode.UInt16: return (ushort)this;
case TypeCode.UInt32: return (uint)this; case TypeCode.UInt32: return (uint)this;
case TypeCode.UInt64: return (long)this; case TypeCode.UInt64: return (long)this;
case TypeCode.Object: return this; case TypeCode.Object: return this;
default: default:
throw new NotSupportedException(); throw new NotSupportedException();
} }
} }
ushort IConvertible.ToUInt16(IFormatProvider provider) => (ushort)this; ushort IConvertible.ToUInt16(IFormatProvider provider) => (ushort)this;
uint IConvertible.ToUInt32(IFormatProvider provider) => (uint)this; uint IConvertible.ToUInt32(IFormatProvider provider) => (uint)this;
ulong IConvertible.ToUInt64(IFormatProvider provider) => (ulong)this; ulong IConvertible.ToUInt64(IFormatProvider provider) => (ulong)this;
/// <summary> /// <summary>
/// Convert to a long if possible, returning true. /// Convert to a long if possible, returning true.
/// ///
/// Returns false otherwise. /// Returns false otherwise.
/// </summary> /// </summary>
/// <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)
{ {
var blob = valueBlob; var blob = valueBlob;
if (blob == IntegerSentinel) if (blob == IntegerSentinel)
{ {
val = valueInt64; val = valueInt64;
return true; return true;
} }
if (blob == null) if (blob == null)
{ {
// in redis-land 0 approx. equal null; so roll with it // in redis-land 0 approx. equal null; so roll with it
val = 0; val = 0;
return true; return true;
} }
return TryParseInt64(blob, 0, blob.Length, out val); return TryParseInt64(blob, 0, blob.Length, out val);
} }
/// <summary> /// <summary>
/// Convert to a int if possible, returning true. /// Convert to a int if possible, returning true.
/// ///
/// Returns false otherwise. /// Returns false otherwise.
/// </summary> /// </summary>
/// <param name="val">The <see cref="int"/> value, if conversion was possible.</param> /// <param name="val">The <see cref="int"/> value, if conversion was possible.</param>
public bool TryParse(out int val) public bool TryParse(out int val)
{ {
if (!TryParse(out long l) || l > int.MaxValue || l < int.MinValue) if (!TryParse(out long l) || l > int.MaxValue || l < int.MinValue)
{ {
val = 0; val = 0;
return false; return false;
} }
val = (int)l; val = (int)l;
return true; return true;
} }
/// <summary> /// <summary>
/// Convert to a double if possible, returning true. /// Convert to a double if possible, returning true.
/// ///
/// Returns false otherwise. /// Returns false otherwise.
/// </summary> /// </summary>
/// <param name="val">The <see cref="double"/> value, if conversion was possible.</param> /// <param name="val">The <see cref="double"/> value, if conversion was possible.</param>
public bool TryParse(out double val) public bool TryParse(out double val)
{ {
var blob = valueBlob; var blob = valueBlob;
if (blob == IntegerSentinel) if (blob == IntegerSentinel)
{ {
val = valueInt64; val = valueInt64;
return true; return true;
} }
if (blob == null) if (blob == null)
{ {
// in redis-land 0 approx. equal null; so roll with it // in redis-land 0 approx. equal null; so roll with it
val = 0; val = 0;
return true; return true;
} }
return TryParseDouble(blob, out val); return TryParseDouble(blob, out val);
} }
} }
internal static class ReflectionExtensions internal static class ReflectionExtensions
{ {
#if NETSTANDARD1_5 #if NETSTANDARD1_5
internal static TypeCode GetTypeCode(this Type type) internal static TypeCode GetTypeCode(this Type type)
{ {
if (type == null) return TypeCode.Empty; if (type == null) return TypeCode.Empty;
if (typeCodeLookup.TryGetValue(type, out TypeCode result)) return result; if (typeCodeLookup.TryGetValue(type, out TypeCode result)) return result;
if (type.GetTypeInfo().IsEnum) if (type.GetTypeInfo().IsEnum)
{ {
type = Enum.GetUnderlyingType(type); type = Enum.GetUnderlyingType(type);
if (typeCodeLookup.TryGetValue(type, out result)) return result; if (typeCodeLookup.TryGetValue(type, out result)) return result;
} }
return TypeCode.Object; return TypeCode.Object;
} }
private static readonly Dictionary<Type, TypeCode> typeCodeLookup = new Dictionary<Type, TypeCode> private static readonly Dictionary<Type, TypeCode> typeCodeLookup = new Dictionary<Type, TypeCode>
{ {
{typeof(bool), TypeCode.Boolean }, {typeof(bool), TypeCode.Boolean },
{typeof(byte), TypeCode.Byte }, {typeof(byte), TypeCode.Byte },
{typeof(char), TypeCode.Char}, {typeof(char), TypeCode.Char},
{typeof(DateTime), TypeCode.DateTime}, {typeof(DateTime), TypeCode.DateTime},
{typeof(decimal), TypeCode.Decimal}, {typeof(decimal), TypeCode.Decimal},
{typeof(double), TypeCode.Double }, {typeof(double), TypeCode.Double },
{typeof(short), TypeCode.Int16 }, {typeof(short), TypeCode.Int16 },
{typeof(int), TypeCode.Int32 }, {typeof(int), TypeCode.Int32 },
{typeof(long), TypeCode.Int64 }, {typeof(long), TypeCode.Int64 },
{typeof(object), TypeCode.Object}, {typeof(object), TypeCode.Object},
{typeof(sbyte), TypeCode.SByte }, {typeof(sbyte), TypeCode.SByte },
{typeof(float), TypeCode.Single }, {typeof(float), TypeCode.Single },
{typeof(string), TypeCode.String }, {typeof(string), TypeCode.String },
{typeof(ushort), TypeCode.UInt16 }, {typeof(ushort), TypeCode.UInt16 },
{typeof(uint), TypeCode.UInt32 }, {typeof(uint), TypeCode.UInt32 },
{typeof(ulong), TypeCode.UInt64 }, {typeof(ulong), TypeCode.UInt64 },
}; };
#else #else
internal static TypeCode GetTypeCode(this Type type) internal static TypeCode GetTypeCode(this Type type)
{ {
return Type.GetTypeCode(type); return Type.GetTypeCode(type);
} }
#endif #endif
} }
} }
\ No newline at end of file
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Reflection.Emit; using System.Reflection.Emit;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
internal static class ScriptParameterMapper internal static class ScriptParameterMapper
{ {
public struct ScriptParameters public struct ScriptParameters
{ {
public RedisKey[] Keys; public RedisKey[] Keys;
public RedisValue[] Arguments; public RedisValue[] Arguments;
public static readonly ConstructorInfo Cons = typeof(ScriptParameters).GetConstructor(new[] { typeof(RedisKey[]), typeof(RedisValue[]) }); public static readonly ConstructorInfo Cons = typeof(ScriptParameters).GetConstructor(new[] { typeof(RedisKey[]), typeof(RedisValue[]) });
public ScriptParameters(RedisKey[] keys, RedisValue[] args) public ScriptParameters(RedisKey[] keys, RedisValue[] args)
{ {
Keys = keys; Keys = keys;
Arguments = args; Arguments = args;
} }
} }
private static readonly Regex ParameterExtractor = new Regex(@"@(?<paramName> ([a-z]|_) ([a-z]|_|\d)*)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); private static readonly Regex ParameterExtractor = new Regex(@"@(?<paramName> ([a-z]|_) ([a-z]|_|\d)*)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
private static string[] ExtractParameters(string script) private static string[] ExtractParameters(string script)
{ {
var ps = ParameterExtractor.Matches(script); var ps = ParameterExtractor.Matches(script);
if (ps.Count == 0) return null; if (ps.Count == 0) return null;
var ret = new HashSet<string>(); var ret = new HashSet<string>();
for (var i = 0; i < ps.Count; i++) for (var i = 0; i < ps.Count; i++)
{ {
var c = ps[i]; var c = ps[i];
var ix = c.Index - 1; var ix = c.Index - 1;
if (ix >= 0) if (ix >= 0)
{ {
var prevChar = script[ix]; var prevChar = script[ix];
// don't consider this a parameter if it's in the middle of word (ie. if it's preceeded by a letter) // don't consider this a parameter if it's in the middle of word (ie. if it's preceeded by a letter)
if (char.IsLetterOrDigit(prevChar) || prevChar == '_') continue; if (char.IsLetterOrDigit(prevChar) || prevChar == '_') continue;
// this is an escape, ignore it // this is an escape, ignore it
if (prevChar == '@') continue; if (prevChar == '@') continue;
} }
var n = c.Groups["paramName"].Value; var n = c.Groups["paramName"].Value;
if (!ret.Contains(n)) ret.Add(n); if (!ret.Contains(n)) ret.Add(n);
} }
return ret.ToArray(); return ret.ToArray();
} }
private static string MakeOrdinalScriptWithoutKeys(string rawScript, string[] args) private static string MakeOrdinalScriptWithoutKeys(string rawScript, string[] args)
{ {
var ps = ParameterExtractor.Matches(rawScript); var ps = ParameterExtractor.Matches(rawScript);
if (ps.Count == 0) return rawScript; if (ps.Count == 0) return rawScript;
var ret = new StringBuilder(); var ret = new StringBuilder();
var upTo = 0; var upTo = 0;
for (var i = 0; i < ps.Count; i++) for (var i = 0; i < ps.Count; i++)
{ {
var capture = ps[i]; var capture = ps[i];
var name = capture.Groups["paramName"].Value; var name = capture.Groups["paramName"].Value;
var ix = capture.Index; var ix = capture.Index;
ret.Append(rawScript, upTo, ix - upTo); ret.Append(rawScript, upTo, ix - upTo);
var argIx = Array.IndexOf(args, name); var argIx = Array.IndexOf(args, name);
if (argIx != -1) if (argIx != -1)
{ {
ret.Append("ARGV["); ret.Append("ARGV[");
ret.Append(argIx + 1); ret.Append(argIx + 1);
ret.Append("]"); ret.Append("]");
} }
else else
{ {
var isEscape = false; var isEscape = false;
var prevIx = capture.Index - 1; var prevIx = capture.Index - 1;
if (prevIx >= 0) if (prevIx >= 0)
{ {
var prevChar = rawScript[prevIx]; var prevChar = rawScript[prevIx];
isEscape = prevChar == '@'; isEscape = prevChar == '@';
} }
if (isEscape) if (isEscape)
{ {
// strip the @ off, so just the one triggering the escape exists // strip the @ off, so just the one triggering the escape exists
ret.Append(capture.Groups["paramName"].Value); ret.Append(capture.Groups["paramName"].Value);
} }
else else
{ {
ret.Append(capture.Value); ret.Append(capture.Value);
} }
} }
upTo = capture.Index + capture.Length; upTo = capture.Index + capture.Length;
} }
ret.Append(rawScript, upTo, rawScript.Length - upTo); ret.Append(rawScript, upTo, rawScript.Length - upTo);
return ret.ToString(); return ret.ToString();
} }
private static void LoadMember(ILGenerator il, MemberInfo member) private static void LoadMember(ILGenerator il, MemberInfo member)
{ {
// stack starts: // stack starts:
// T(*?) // T(*?)
if (member is FieldInfo asField) if (member is FieldInfo asField)
{ {
il.Emit(OpCodes.Ldfld, asField); // typeof(member) il.Emit(OpCodes.Ldfld, asField); // typeof(member)
return; return;
} }
if (member is PropertyInfo asProp) if (member is PropertyInfo asProp)
{ {
var getter = asProp.GetGetMethod(); var getter = asProp.GetGetMethod();
if (getter.IsVirtual) if (getter.IsVirtual)
{ {
il.Emit(OpCodes.Callvirt, getter); // typeof(member) il.Emit(OpCodes.Callvirt, getter); // typeof(member)
} }
else else
{ {
il.Emit(OpCodes.Call, getter); // typeof(member) il.Emit(OpCodes.Call, getter); // typeof(member)
} }
return; return;
} }
throw new Exception("Should't be possible"); throw new Exception("Should't be possible");
} }
private static readonly MethodInfo RedisValue_FromInt = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(int) }); private static readonly MethodInfo RedisValue_FromInt = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(int) });
private static readonly MethodInfo RedisValue_FromNullableInt = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(int?) }); private static readonly MethodInfo RedisValue_FromNullableInt = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(int?) });
private static readonly MethodInfo RedisValue_FromLong = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(long) }); private static readonly MethodInfo RedisValue_FromLong = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(long) });
private static readonly MethodInfo RedisValue_FromNullableLong = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(long?) }); private static readonly MethodInfo RedisValue_FromNullableLong = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(long?) });
private static readonly MethodInfo RedisValue_FromDouble= typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(double) }); private static readonly MethodInfo RedisValue_FromDouble= typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(double) });
private static readonly MethodInfo RedisValue_FromNullableDouble = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(double?) }); private static readonly MethodInfo RedisValue_FromNullableDouble = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(double?) });
private static readonly MethodInfo RedisValue_FromString = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(string) }); private static readonly MethodInfo RedisValue_FromString = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(string) });
private static readonly MethodInfo RedisValue_FromByteArray = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(byte[]) }); private static readonly MethodInfo RedisValue_FromByteArray = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(byte[]) });
private static readonly MethodInfo RedisValue_FromBool = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(bool) }); private static readonly MethodInfo RedisValue_FromBool = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(bool) });
private static readonly MethodInfo RedisValue_FromNullableBool = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(bool?) }); private static readonly MethodInfo RedisValue_FromNullableBool = typeof(RedisValue).GetMethod("op_Implicit", new[] { typeof(bool?) });
private static readonly MethodInfo RedisKey_AsRedisValue = typeof(RedisKey).GetMethod("AsRedisValue", BindingFlags.NonPublic | BindingFlags.Instance); private static readonly MethodInfo RedisKey_AsRedisValue = typeof(RedisKey).GetMethod("AsRedisValue", BindingFlags.NonPublic | BindingFlags.Instance);
private static void ConvertToRedisValue(MemberInfo member, ILGenerator il, LocalBuilder needsPrefixBool, ref LocalBuilder redisKeyLoc) private static void ConvertToRedisValue(MemberInfo member, ILGenerator il, LocalBuilder needsPrefixBool, ref LocalBuilder redisKeyLoc)
{ {
// stack starts: // stack starts:
// typeof(member) // typeof(member)
var t = member is FieldInfo ? ((FieldInfo)member).FieldType : ((PropertyInfo)member).PropertyType; var t = member is FieldInfo ? ((FieldInfo)member).FieldType : ((PropertyInfo)member).PropertyType;
if (t == typeof(RedisValue)) if (t == typeof(RedisValue))
{ {
// They've already converted for us, don't do anything // They've already converted for us, don't do anything
return; return;
} }
if (t == typeof(RedisKey)) if (t == typeof(RedisKey))
{ {
redisKeyLoc = redisKeyLoc ?? il.DeclareLocal(typeof(RedisKey)); redisKeyLoc = redisKeyLoc ?? il.DeclareLocal(typeof(RedisKey));
PrefixIfNeeded(il, needsPrefixBool, ref redisKeyLoc); // RedisKey PrefixIfNeeded(il, needsPrefixBool, ref redisKeyLoc); // RedisKey
il.Emit(OpCodes.Stloc, redisKeyLoc); // --empty-- il.Emit(OpCodes.Stloc, redisKeyLoc); // --empty--
il.Emit(OpCodes.Ldloca, redisKeyLoc); // RedisKey* il.Emit(OpCodes.Ldloca, redisKeyLoc); // RedisKey*
il.Emit(OpCodes.Call, RedisKey_AsRedisValue); // RedisValue il.Emit(OpCodes.Call, RedisKey_AsRedisValue); // RedisValue
return; return;
} }
MethodInfo convertOp = null; MethodInfo convertOp = null;
if (t == typeof(int)) convertOp = RedisValue_FromInt; if (t == typeof(int)) convertOp = RedisValue_FromInt;
if (t == typeof(int?)) convertOp = RedisValue_FromNullableInt; if (t == typeof(int?)) convertOp = RedisValue_FromNullableInt;
if (t == typeof(long)) convertOp = RedisValue_FromLong; if (t == typeof(long)) convertOp = RedisValue_FromLong;
if (t == typeof(long?)) convertOp = RedisValue_FromNullableLong; if (t == typeof(long?)) convertOp = RedisValue_FromNullableLong;
if (t == typeof(double)) convertOp = RedisValue_FromDouble; if (t == typeof(double)) convertOp = RedisValue_FromDouble;
if (t == typeof(double?)) convertOp = RedisValue_FromNullableDouble; if (t == typeof(double?)) convertOp = RedisValue_FromNullableDouble;
if (t == typeof(string)) convertOp = RedisValue_FromString; if (t == typeof(string)) convertOp = RedisValue_FromString;
if (t == typeof(byte[])) convertOp = RedisValue_FromByteArray; if (t == typeof(byte[])) convertOp = RedisValue_FromByteArray;
if (t == typeof(bool)) convertOp = RedisValue_FromBool; if (t == typeof(bool)) convertOp = RedisValue_FromBool;
if (t == typeof(bool?)) convertOp = RedisValue_FromNullableBool; if (t == typeof(bool?)) convertOp = RedisValue_FromNullableBool;
il.Emit(OpCodes.Call, convertOp); il.Emit(OpCodes.Call, convertOp);
// stack ends: // stack ends:
// RedisValue // RedisValue
} }
/// <summary> /// <summary>
/// Turns a script with @namedParameters into a LuaScript that can be executed /// Turns a script with @namedParameters into a LuaScript that can be executed
/// against a given IDatabase(Async) object /// against a given IDatabase(Async) object
/// </summary> /// </summary>
/// <param name="script">The script to prepare.</param> /// <param name="script">The script to prepare.</param>
public static LuaScript PrepareScript(string script) public static LuaScript PrepareScript(string script)
{ {
var ps = ExtractParameters(script); var ps = ExtractParameters(script);
var ordinalScript = MakeOrdinalScriptWithoutKeys(script, ps); var ordinalScript = MakeOrdinalScriptWithoutKeys(script, ps);
return new LuaScript(script, ordinalScript, ps); return new LuaScript(script, ordinalScript, ps);
} }
private static readonly HashSet<Type> ConvertableTypes = private static readonly HashSet<Type> ConvertableTypes =
new HashSet<Type> { new HashSet<Type> {
typeof(int), typeof(int),
typeof(int?), typeof(int?),
typeof(long), typeof(long),
typeof(long?), typeof(long?),
typeof(double), typeof(double),
typeof(double?), typeof(double?),
typeof(string), typeof(string),
typeof(byte[]), typeof(byte[]),
typeof(bool), typeof(bool),
typeof(bool?), typeof(bool?),
typeof(RedisKey), typeof(RedisKey),
typeof(RedisValue) typeof(RedisValue)
}; };
/// <summary> /// <summary>
/// Determines whether or not the given type can be used to provide parameters for the given LuaScript. /// Determines whether or not the given type can be used to provide parameters for the given LuaScript.
/// </summary> /// </summary>
/// <param name="t">The type of the parameter.</param> /// <param name="t">The type of the parameter.</param>
/// <param name="script">The script to match against.</param> /// <param name="script">The script to match against.</param>
/// <param name="missingMember">The first missing member, if any.</param> /// <param name="missingMember">The first missing member, if any.</param>
/// <param name="badTypeMember">The first type mismatched member, if any.</param> /// <param name="badTypeMember">The first type mismatched member, if any.</param>
public static bool IsValidParameterHash(Type t, LuaScript script, out string missingMember, out string badTypeMember) public static bool IsValidParameterHash(Type t, LuaScript script, out string missingMember, out string badTypeMember)
{ {
for (var i = 0; i < script.Arguments.Length; i++) for (var i = 0; i < script.Arguments.Length; i++)
{ {
var argName = script.Arguments[i]; var argName = script.Arguments[i];
var member = t.GetMember(argName).SingleOrDefault(m => m is PropertyInfo || m is FieldInfo); var member = t.GetMember(argName).SingleOrDefault(m => m is PropertyInfo || m is FieldInfo);
if (member == null) if (member == null)
{ {
missingMember = argName; missingMember = argName;
badTypeMember = null; badTypeMember = null;
return false; return false;
} }
var memberType = member is FieldInfo ? ((FieldInfo)member).FieldType : ((PropertyInfo)member).PropertyType; var memberType = member is FieldInfo ? ((FieldInfo)member).FieldType : ((PropertyInfo)member).PropertyType;
if(!ConvertableTypes.Contains(memberType)){ if(!ConvertableTypes.Contains(memberType)){
missingMember = null; missingMember = null;
badTypeMember = argName; badTypeMember = argName;
return false; return false;
} }
} }
missingMember = badTypeMember = null; missingMember = badTypeMember = null;
return true; return true;
} }
private static void PrefixIfNeeded(ILGenerator il, LocalBuilder needsPrefixBool, ref LocalBuilder redisKeyLoc) private static void PrefixIfNeeded(ILGenerator il, LocalBuilder needsPrefixBool, ref LocalBuilder redisKeyLoc)
{ {
// top of stack is // top of stack is
// RedisKey // RedisKey
var getVal = typeof(RedisKey?).GetProperty("Value").GetGetMethod(); var getVal = typeof(RedisKey?).GetProperty("Value").GetGetMethod();
var prepend = typeof(RedisKey).GetMethod("Prepend"); var prepend = typeof(RedisKey).GetMethod("Prepend");
var doNothing = il.DefineLabel(); var doNothing = il.DefineLabel();
redisKeyLoc = redisKeyLoc ?? il.DeclareLocal(typeof(RedisKey)); redisKeyLoc = redisKeyLoc ?? il.DeclareLocal(typeof(RedisKey));
il.Emit(OpCodes.Ldloc, needsPrefixBool); // RedisKey bool il.Emit(OpCodes.Ldloc, needsPrefixBool); // RedisKey bool
il.Emit(OpCodes.Brfalse, doNothing); // RedisKey il.Emit(OpCodes.Brfalse, doNothing); // RedisKey
il.Emit(OpCodes.Stloc, redisKeyLoc); // --empty-- il.Emit(OpCodes.Stloc, redisKeyLoc); // --empty--
il.Emit(OpCodes.Ldloca, redisKeyLoc); // RedisKey* il.Emit(OpCodes.Ldloca, redisKeyLoc); // RedisKey*
il.Emit(OpCodes.Ldarga_S, 1); // RedisKey* RedisKey?* il.Emit(OpCodes.Ldarga_S, 1); // RedisKey* RedisKey?*
il.Emit(OpCodes.Call, getVal); // RedisKey* RedisKey il.Emit(OpCodes.Call, getVal); // RedisKey* RedisKey
il.Emit(OpCodes.Call, prepend); // RedisKey il.Emit(OpCodes.Call, prepend); // RedisKey
il.MarkLabel(doNothing); // RedisKey il.MarkLabel(doNothing); // RedisKey
} }
/// <summary> /// <summary>
/// Creates a Func that extracts parameters from the given type for use by a LuaScript. /// Creates a Func that extracts parameters from the given type for use by a LuaScript.
/// ///
/// Members that are RedisKey's get extracted to be passed in as keys to redis; all members that /// Members that are RedisKey's get extracted to be passed in as keys to redis; all members that
/// appear in the script get extracted as RedisValue arguments to be sent up as args. /// appear in the script get extracted as RedisValue arguments to be sent up as args.
/// ///
/// We send all values as arguments so we don't have to prepare the same script for different parameter /// We send all values as arguments so we don't have to prepare the same script for different parameter
/// types. /// types.
/// ///
/// The created Func takes a RedisKey, which will be prefixed to all keys (and arguments of type RedisKey) for /// The created Func takes a RedisKey, which will be prefixed to all keys (and arguments of type RedisKey) for
/// keyspace isolation. /// keyspace isolation.
/// </summary> /// </summary>
/// <param name="t">The type to extract for.</param> /// <param name="t">The type to extract for.</param>
/// <param name="script">The script to extract for.</param> /// <param name="script">The script to extract for.</param>
public static Func<object, RedisKey?, ScriptParameters> GetParameterExtractor(Type t, LuaScript script) public static Func<object, RedisKey?, ScriptParameters> GetParameterExtractor(Type t, LuaScript script)
{ {
if (!IsValidParameterHash(t, script, out _, out _)) throw new Exception("Shouldn't be possible"); if (!IsValidParameterHash(t, script, out _, out _)) throw new Exception("Shouldn't be possible");
var keys = new List<MemberInfo>(); var keys = new List<MemberInfo>();
var args = new List<MemberInfo>(); var args = new List<MemberInfo>();
for (var i = 0; i < script.Arguments.Length; i++) for (var i = 0; i < script.Arguments.Length; i++)
{ {
var argName = script.Arguments[i]; var argName = script.Arguments[i];
var member = t.GetMember(argName).SingleOrDefault(m => m is PropertyInfo || m is FieldInfo); var member = t.GetMember(argName).SingleOrDefault(m => m is PropertyInfo || m is FieldInfo);
var memberType = member is FieldInfo ? ((FieldInfo)member).FieldType : ((PropertyInfo)member).PropertyType; var memberType = member is FieldInfo ? ((FieldInfo)member).FieldType : ((PropertyInfo)member).PropertyType;
if (memberType == typeof(RedisKey)) if (memberType == typeof(RedisKey))
{ {
keys.Add(member); keys.Add(member);
} }
args.Add(member); args.Add(member);
} }
var nullableRedisKeyHasValue = typeof(RedisKey?).GetProperty("HasValue").GetGetMethod(); var nullableRedisKeyHasValue = typeof(RedisKey?).GetProperty("HasValue").GetGetMethod();
var dyn = new DynamicMethod("ParameterExtractor_" + t.FullName + "_" + script.OriginalScript.GetHashCode(), typeof(ScriptParameters), new[] { typeof(object), typeof(RedisKey?) }, restrictedSkipVisibility: true); var dyn = new DynamicMethod("ParameterExtractor_" + t.FullName + "_" + script.OriginalScript.GetHashCode(), typeof(ScriptParameters), new[] { typeof(object), typeof(RedisKey?) }, restrictedSkipVisibility: true);
var il = dyn.GetILGenerator(); var il = dyn.GetILGenerator();
// only init'd if we use it // only init'd if we use it
LocalBuilder redisKeyLoc = null; LocalBuilder redisKeyLoc = null;
var loc = il.DeclareLocal(t); var loc = il.DeclareLocal(t);
il.Emit(OpCodes.Ldarg_0); // object il.Emit(OpCodes.Ldarg_0); // object
#if NETSTANDARD1_5 #if NETSTANDARD1_5
if (t.GetTypeInfo().IsValueType) if (t.GetTypeInfo().IsValueType)
#else #else
if (t.IsValueType) if (t.IsValueType)
#endif #endif
{ {
il.Emit(OpCodes.Unbox_Any, t); // T il.Emit(OpCodes.Unbox_Any, t); // T
} }
else else
{ {
il.Emit(OpCodes.Castclass, t); // T il.Emit(OpCodes.Castclass, t); // T
} }
il.Emit(OpCodes.Stloc, loc); // --empty-- il.Emit(OpCodes.Stloc, loc); // --empty--
var needsKeyPrefixLoc = il.DeclareLocal(typeof(bool)); var needsKeyPrefixLoc = il.DeclareLocal(typeof(bool));
il.Emit(OpCodes.Ldarga_S, 1); // RedisKey?* il.Emit(OpCodes.Ldarga_S, 1); // RedisKey?*
il.Emit(OpCodes.Call, nullableRedisKeyHasValue); // bool il.Emit(OpCodes.Call, nullableRedisKeyHasValue); // bool
il.Emit(OpCodes.Stloc, needsKeyPrefixLoc); // --empty-- il.Emit(OpCodes.Stloc, needsKeyPrefixLoc); // --empty--
if (keys.Count == 0) if (keys.Count == 0)
{ {
// if there are no keys, don't allocate // if there are no keys, don't allocate
il.Emit(OpCodes.Ldnull); // null il.Emit(OpCodes.Ldnull); // null
} }
else else
{ {
il.Emit(OpCodes.Ldc_I4, keys.Count); // int il.Emit(OpCodes.Ldc_I4, keys.Count); // int
il.Emit(OpCodes.Newarr, typeof(RedisKey)); // RedisKey[] il.Emit(OpCodes.Newarr, typeof(RedisKey)); // RedisKey[]
} }
for (var i = 0; i < keys.Count; i++) for (var i = 0; i < keys.Count; i++)
{ {
il.Emit(OpCodes.Dup); // RedisKey[] RedisKey[] il.Emit(OpCodes.Dup); // RedisKey[] RedisKey[]
il.Emit(OpCodes.Ldc_I4, i); // RedisKey[] RedisKey[] int il.Emit(OpCodes.Ldc_I4, i); // RedisKey[] RedisKey[] int
#if NETSTANDARD1_5 #if NETSTANDARD1_5
if (t.GetTypeInfo().IsValueType) if (t.GetTypeInfo().IsValueType)
#else #else
if (t.IsValueType) if (t.IsValueType)
#endif #endif
{ {
il.Emit(OpCodes.Ldloca, loc); // RedisKey[] RedisKey[] int T* il.Emit(OpCodes.Ldloca, loc); // RedisKey[] RedisKey[] int T*
} }
else else
{ {
il.Emit(OpCodes.Ldloc, loc); // RedisKey[] RedisKey[] int T il.Emit(OpCodes.Ldloc, loc); // RedisKey[] RedisKey[] int T
} }
LoadMember(il, keys[i]); // RedisKey[] RedisKey[] int RedisKey LoadMember(il, keys[i]); // RedisKey[] RedisKey[] int RedisKey
PrefixIfNeeded(il, needsKeyPrefixLoc, ref redisKeyLoc); // RedisKey[] RedisKey[] int RedisKey PrefixIfNeeded(il, needsKeyPrefixLoc, ref redisKeyLoc); // RedisKey[] RedisKey[] int RedisKey
il.Emit(OpCodes.Stelem, typeof(RedisKey)); // RedisKey[] il.Emit(OpCodes.Stelem, typeof(RedisKey)); // RedisKey[]
} }
if (args.Count == 0) if (args.Count == 0)
{ {
// if there are no args, don't allocate // if there are no args, don't allocate
il.Emit(OpCodes.Ldnull); // RedisKey[] null il.Emit(OpCodes.Ldnull); // RedisKey[] null
} }
else else
{ {
il.Emit(OpCodes.Ldc_I4, args.Count); // RedisKey[] int il.Emit(OpCodes.Ldc_I4, args.Count); // RedisKey[] int
il.Emit(OpCodes.Newarr, typeof(RedisValue)); // RedisKey[] RedisValue[] il.Emit(OpCodes.Newarr, typeof(RedisValue)); // RedisKey[] RedisValue[]
} }
for (var i = 0; i < args.Count; i++) for (var i = 0; i < args.Count; i++)
{ {
il.Emit(OpCodes.Dup); // RedisKey[] RedisValue[] RedisValue[] il.Emit(OpCodes.Dup); // RedisKey[] RedisValue[] RedisValue[]
il.Emit(OpCodes.Ldc_I4, i); // RedisKey[] RedisValue[] RedisValue[] int il.Emit(OpCodes.Ldc_I4, i); // RedisKey[] RedisValue[] RedisValue[] int
#if NETSTANDARD1_5 #if NETSTANDARD1_5
if (t.GetTypeInfo().IsValueType) if (t.GetTypeInfo().IsValueType)
#else #else
if (t.IsValueType) if (t.IsValueType)
#endif #endif
{ {
il.Emit(OpCodes.Ldloca, loc); // RedisKey[] RedisValue[] RedisValue[] int T* il.Emit(OpCodes.Ldloca, loc); // RedisKey[] RedisValue[] RedisValue[] int T*
} }
else else
{ {
il.Emit(OpCodes.Ldloc, loc); // RedisKey[] RedisValue[] RedisValue[] int T il.Emit(OpCodes.Ldloc, loc); // RedisKey[] RedisValue[] RedisValue[] int T
} }
var member = args[i]; var member = args[i];
LoadMember(il, member); // RedisKey[] RedisValue[] RedisValue[] int memberType LoadMember(il, member); // RedisKey[] RedisValue[] RedisValue[] int memberType
ConvertToRedisValue(member, il, needsKeyPrefixLoc, ref redisKeyLoc); // RedisKey[] RedisValue[] RedisValue[] int RedisValue ConvertToRedisValue(member, il, needsKeyPrefixLoc, ref redisKeyLoc); // RedisKey[] RedisValue[] RedisValue[] int RedisValue
il.Emit(OpCodes.Stelem, typeof(RedisValue)); // RedisKey[] RedisValue[] il.Emit(OpCodes.Stelem, typeof(RedisValue)); // RedisKey[] RedisValue[]
} }
il.Emit(OpCodes.Newobj, ScriptParameters.Cons); // ScriptParameters il.Emit(OpCodes.Newobj, ScriptParameters.Cons); // ScriptParameters
il.Emit(OpCodes.Ret); // --empty-- il.Emit(OpCodes.Ret); // --empty--
var ret = (Func<object, RedisKey?, ScriptParameters>)dyn.CreateDelegate(typeof(Func<object, RedisKey?, ScriptParameters>)); var ret = (Func<object, RedisKey?, ScriptParameters>)dyn.CreateDelegate(typeof(Func<object, RedisKey?, ScriptParameters>));
return ret; return ret;
} }
} }
} }
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